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