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