github.com/demonoid81/containerd@v1.3.4/snapshots/btrfs/btrfs.go (about) 1 // +build linux,!no_btrfs 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 btrfs 20 21 import ( 22 "context" 23 "fmt" 24 "os" 25 "path/filepath" 26 "strings" 27 28 "github.com/containerd/btrfs" 29 "github.com/containerd/containerd/log" 30 "github.com/containerd/containerd/mount" 31 "github.com/containerd/containerd/platforms" 32 "github.com/containerd/containerd/plugin" 33 "github.com/containerd/containerd/snapshots" 34 "github.com/containerd/containerd/snapshots/storage" 35 "github.com/containerd/continuity/fs" 36 37 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 38 "github.com/pkg/errors" 39 "github.com/sirupsen/logrus" 40 ) 41 42 func init() { 43 plugin.Register(&plugin.Registration{ 44 ID: "btrfs", 45 Type: plugin.SnapshotPlugin, 46 InitFn: func(ic *plugin.InitContext) (interface{}, error) { 47 ic.Meta.Platforms = []ocispec.Platform{platforms.DefaultSpec()} 48 ic.Meta.Exports = map[string]string{"root": ic.Root} 49 return NewSnapshotter(ic.Root) 50 }, 51 }) 52 } 53 54 type snapshotter struct { 55 device string // device of the root 56 root string // root provides paths for internal storage. 57 ms *storage.MetaStore 58 } 59 60 // NewSnapshotter returns a Snapshotter using btrfs. Uses the provided 61 // root directory for snapshots and stores the metadata in 62 // a file in the provided root. 63 // root needs to be a mount point of btrfs. 64 func NewSnapshotter(root string) (snapshots.Snapshotter, error) { 65 // If directory does not exist, create it 66 if _, err := os.Stat(root); err != nil { 67 if !os.IsNotExist(err) { 68 return nil, err 69 } 70 if err := os.Mkdir(root, 0755); err != nil { 71 return nil, err 72 } 73 } 74 75 mnt, err := mount.Lookup(root) 76 if err != nil { 77 return nil, err 78 } 79 if mnt.FSType != "btrfs" { 80 return nil, errors.Wrapf(plugin.ErrSkipPlugin, "path %s (%s) must be a btrfs filesystem to be used with the btrfs snapshotter", root, mnt.FSType) 81 } 82 var ( 83 active = filepath.Join(root, "active") 84 view = filepath.Join(root, "view") 85 snapshots = filepath.Join(root, "snapshots") 86 ) 87 88 for _, path := range []string{ 89 active, 90 view, 91 snapshots, 92 } { 93 if err := os.Mkdir(path, 0755); err != nil && !os.IsExist(err) { 94 return nil, err 95 } 96 } 97 ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db")) 98 if err != nil { 99 return nil, err 100 } 101 102 return &snapshotter{ 103 device: mnt.Source, 104 root: root, 105 ms: ms, 106 }, nil 107 } 108 109 // Stat returns the info for an active or committed snapshot by name or 110 // key. 111 // 112 // Should be used for parent resolution, existence checks and to discern 113 // the kind of snapshot. 114 func (b *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) { 115 ctx, t, err := b.ms.TransactionContext(ctx, false) 116 if err != nil { 117 return snapshots.Info{}, err 118 } 119 defer t.Rollback() 120 _, info, _, err := storage.GetInfo(ctx, key) 121 if err != nil { 122 return snapshots.Info{}, err 123 } 124 125 return info, nil 126 } 127 128 func (b *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) { 129 ctx, t, err := b.ms.TransactionContext(ctx, true) 130 if err != nil { 131 return snapshots.Info{}, err 132 } 133 134 info, err = storage.UpdateInfo(ctx, info, fieldpaths...) 135 if err != nil { 136 t.Rollback() 137 return snapshots.Info{}, err 138 } 139 140 if err := t.Commit(); err != nil { 141 return snapshots.Info{}, err 142 } 143 144 return info, nil 145 } 146 147 // Usage retrieves the disk usage of the top-level snapshot. 148 func (b *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { 149 return b.usage(ctx, key) 150 } 151 152 func (b *snapshotter) usage(ctx context.Context, key string) (snapshots.Usage, error) { 153 ctx, t, err := b.ms.TransactionContext(ctx, false) 154 if err != nil { 155 return snapshots.Usage{}, err 156 } 157 id, info, usage, err := storage.GetInfo(ctx, key) 158 var parentID string 159 if err == nil && info.Kind == snapshots.KindActive && info.Parent != "" { 160 parentID, _, _, err = storage.GetInfo(ctx, info.Parent) 161 162 } 163 t.Rollback() // transaction no longer needed at this point. 164 165 if err != nil { 166 return snapshots.Usage{}, err 167 } 168 169 if info.Kind == snapshots.KindActive { 170 var du fs.Usage 171 p := filepath.Join(b.root, "active", id) 172 if parentID != "" { 173 du, err = fs.DiffUsage(ctx, filepath.Join(b.root, "snapshots", parentID), p) 174 } else { 175 du, err = fs.DiskUsage(ctx, p) 176 } 177 if err != nil { 178 // TODO(stevvooe): Consider not reporting an error in this case. 179 return snapshots.Usage{}, err 180 } 181 182 usage = snapshots.Usage(du) 183 } 184 185 return usage, nil 186 } 187 188 // Walk the committed snapshots. 189 func (b *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshots.Info) error) error { 190 ctx, t, err := b.ms.TransactionContext(ctx, false) 191 if err != nil { 192 return err 193 } 194 defer t.Rollback() 195 return storage.WalkInfo(ctx, fn) 196 } 197 198 func (b *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { 199 return b.makeSnapshot(ctx, snapshots.KindActive, key, parent, opts) 200 } 201 202 func (b *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { 203 return b.makeSnapshot(ctx, snapshots.KindView, key, parent, opts) 204 } 205 206 func (b *snapshotter) makeSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) { 207 ctx, t, err := b.ms.TransactionContext(ctx, true) 208 if err != nil { 209 return nil, err 210 } 211 defer func() { 212 if err != nil && t != nil { 213 if rerr := t.Rollback(); rerr != nil { 214 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 215 } 216 } 217 }() 218 219 s, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...) 220 if err != nil { 221 return nil, err 222 } 223 224 target := filepath.Join(b.root, strings.ToLower(s.Kind.String()), s.ID) 225 226 if len(s.ParentIDs) == 0 { 227 // create new subvolume 228 // btrfs subvolume create /dir 229 if err = btrfs.SubvolCreate(target); err != nil { 230 return nil, err 231 } 232 } else { 233 parentp := filepath.Join(b.root, "snapshots", s.ParentIDs[0]) 234 235 var readonly bool 236 if kind == snapshots.KindView { 237 readonly = true 238 } 239 240 // btrfs subvolume snapshot /parent /subvol 241 if err = btrfs.SubvolSnapshot(target, parentp, readonly); err != nil { 242 return nil, err 243 } 244 } 245 err = t.Commit() 246 t = nil 247 if err != nil { 248 if derr := btrfs.SubvolDelete(target); derr != nil { 249 log.G(ctx).WithError(derr).WithField("subvolume", target).Error("failed to delete subvolume") 250 } 251 return nil, err 252 } 253 254 return b.mounts(target, s) 255 } 256 257 func (b *snapshotter) mounts(dir string, s storage.Snapshot) ([]mount.Mount, error) { 258 var options []string 259 260 // get the subvolume id back out for the mount 261 sid, err := btrfs.SubvolID(dir) 262 if err != nil { 263 return nil, err 264 } 265 266 options = append(options, fmt.Sprintf("subvolid=%d", sid)) 267 268 if s.Kind != snapshots.KindActive { 269 options = append(options, "ro") 270 } 271 272 return []mount.Mount{ 273 { 274 Type: "btrfs", 275 Source: b.device, 276 // NOTE(stevvooe): While it would be nice to use to uuids for 277 // mounts, they don't work reliably if the uuids are missing. 278 Options: options, 279 }, 280 }, nil 281 } 282 283 func (b *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) (err error) { 284 usage, err := b.usage(ctx, key) 285 if err != nil { 286 return errors.Wrap(err, "failed to compute usage") 287 } 288 289 ctx, t, err := b.ms.TransactionContext(ctx, true) 290 if err != nil { 291 return err 292 } 293 defer func() { 294 if err != nil && t != nil { 295 if rerr := t.Rollback(); rerr != nil { 296 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 297 } 298 } 299 }() 300 301 id, err := storage.CommitActive(ctx, key, name, usage, opts...) // TODO(stevvooe): Resolve a usage value for btrfs 302 if err != nil { 303 return errors.Wrap(err, "failed to commit") 304 } 305 306 source := filepath.Join(b.root, "active", id) 307 target := filepath.Join(b.root, "snapshots", id) 308 309 if err := btrfs.SubvolSnapshot(target, source, true); err != nil { 310 return err 311 } 312 313 err = t.Commit() 314 t = nil 315 if err != nil { 316 if derr := btrfs.SubvolDelete(target); derr != nil { 317 log.G(ctx).WithError(derr).WithField("subvolume", target).Error("failed to delete subvolume") 318 } 319 return err 320 } 321 322 if derr := btrfs.SubvolDelete(source); derr != nil { 323 // Log as warning, only needed for cleanup, will not cause name collision 324 log.G(ctx).WithError(derr).WithField("subvolume", source).Warn("failed to delete subvolume") 325 } 326 327 return nil 328 } 329 330 // Mounts returns the mounts for the transaction identified by key. Can be 331 // called on an read-write or readonly transaction. 332 // 333 // This can be used to recover mounts after calling View or Prepare. 334 func (b *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) { 335 ctx, t, err := b.ms.TransactionContext(ctx, false) 336 if err != nil { 337 return nil, err 338 } 339 s, err := storage.GetSnapshot(ctx, key) 340 t.Rollback() 341 if err != nil { 342 return nil, errors.Wrap(err, "failed to get active snapshot") 343 } 344 345 dir := filepath.Join(b.root, strings.ToLower(s.Kind.String()), s.ID) 346 return b.mounts(dir, s) 347 } 348 349 // Remove abandons the transaction identified by key. All resources 350 // associated with the key will be removed. 351 func (b *snapshotter) Remove(ctx context.Context, key string) (err error) { 352 var ( 353 source, removed string 354 readonly bool 355 ) 356 357 ctx, t, err := b.ms.TransactionContext(ctx, true) 358 if err != nil { 359 return err 360 } 361 defer func() { 362 if err != nil && t != nil { 363 if rerr := t.Rollback(); rerr != nil { 364 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 365 } 366 } 367 368 if removed != "" { 369 if derr := btrfs.SubvolDelete(removed); derr != nil { 370 log.G(ctx).WithError(derr).WithField("subvolume", removed).Warn("failed to delete subvolume") 371 } 372 } 373 }() 374 375 id, k, err := storage.Remove(ctx, key) 376 if err != nil { 377 return errors.Wrap(err, "failed to remove snapshot") 378 } 379 380 switch k { 381 case snapshots.KindView: 382 source = filepath.Join(b.root, "view", id) 383 removed = filepath.Join(b.root, "view", "rm-"+id) 384 readonly = true 385 case snapshots.KindActive: 386 source = filepath.Join(b.root, "active", id) 387 removed = filepath.Join(b.root, "active", "rm-"+id) 388 case snapshots.KindCommitted: 389 source = filepath.Join(b.root, "snapshots", id) 390 removed = filepath.Join(b.root, "snapshots", "rm-"+id) 391 readonly = true 392 } 393 394 if err := btrfs.SubvolSnapshot(removed, source, readonly); err != nil { 395 removed = "" 396 return err 397 } 398 399 if err := btrfs.SubvolDelete(source); err != nil { 400 return errors.Wrapf(err, "failed to remove snapshot %v", source) 401 } 402 403 err = t.Commit() 404 t = nil 405 if err != nil { 406 // Attempt to restore source 407 if err1 := btrfs.SubvolSnapshot(source, removed, readonly); err1 != nil { 408 log.G(ctx).WithFields(logrus.Fields{ 409 logrus.ErrorKey: err1, 410 "subvolume": source, 411 "renamed": removed, 412 }).Error("failed to restore subvolume from renamed") 413 // Keep removed to allow for manual restore 414 removed = "" 415 } 416 return err 417 } 418 419 return nil 420 } 421 422 // Close closes the snapshotter 423 func (b *snapshotter) Close() error { 424 return b.ms.Close() 425 }