// Copyright (C) 2022-2023 Luke Shumaker // // SPDX-License-Identifier: GPL-2.0-or-later package btrfs import ( "context" "fmt" "io" "github.com/datawire/dlib/derror" "github.com/datawire/dlib/dlog" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsitem" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsprim" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfstree" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol" "git.lukeshu.com/btrfs-progs-ng/lib/containers" "git.lukeshu.com/btrfs-progs-ng/lib/diskio" ) type FS struct { // You should probably not access .LV directly, except when // implementing special things like fsck. LV btrfsvol.LogicalVolume[*Device] cacheSuperblocks []*diskio.Ref[btrfsvol.PhysicalAddr, btrfstree.Superblock] cacheSuperblock *btrfstree.Superblock cacheNodes containers.Cache[btrfsvol.LogicalAddr, nodeCacheEntry] } var _ diskio.File[btrfsvol.LogicalAddr] = (*FS)(nil) func (fs *FS) AddDevice(ctx context.Context, dev *Device) error { sb, err := dev.Superblock() if err != nil { return err } if err := fs.LV.AddPhysicalVolume(sb.DevItem.DevID, dev); err != nil { return err } fs.cacheSuperblocks = nil fs.cacheSuperblock = nil if err := fs.initDev(*sb); err != nil { dlog.Errorf(ctx, "error: AddDevice: %q: %v", dev.Name(), err) } return nil } func (fs *FS) Name() string { if name := fs.LV.Name(); name != "" { return name } sb, err := fs.Superblock() if err != nil { return fmt.Sprintf("fs_uuid=%v", "(unreadable)") } name := fmt.Sprintf("fs_uuid=%v", sb.FSUUID) fs.LV.SetName(name) return name } func (fs *FS) Size() btrfsvol.LogicalAddr { return fs.LV.Size() } func (fs *FS) ReadAt(p []byte, off btrfsvol.LogicalAddr) (int, error) { return fs.LV.ReadAt(p, off) } func (fs *FS) WriteAt(p []byte, off btrfsvol.LogicalAddr) (int, error) { return fs.LV.WriteAt(p, off) } func (fs *FS) Superblocks() ([]*diskio.Ref[btrfsvol.PhysicalAddr, btrfstree.Superblock], error) { if fs.cacheSuperblocks != nil { return fs.cacheSuperblocks, nil } var ret []*diskio.Ref[btrfsvol.PhysicalAddr, btrfstree.Superblock] devs := fs.LV.PhysicalVolumes() if len(devs) == 0 { return nil, fmt.Errorf("no devices") } for _, dev := range devs { sbs, err := dev.Superblocks() if err != nil { return nil, fmt.Errorf("file %q: %w", dev.Name(), err) } ret = append(ret, sbs...) } fs.cacheSuperblocks = ret return ret, nil } func (fs *FS) Superblock() (*btrfstree.Superblock, error) { if fs.cacheSuperblock != nil { return fs.cacheSuperblock, nil } sbs, err := fs.Superblocks() if err != nil { return nil, err } if len(sbs) == 0 { return nil, fmt.Errorf("no superblocks") } fname := "" sbi := 0 for i, sb := range sbs { if sb.File.Name() != fname { fname = sb.File.Name() sbi = 0 } else { sbi++ } if err := sb.Data.ValidateChecksum(); err != nil { return nil, fmt.Errorf("file %q superblock %v: %w", sb.File.Name(), sbi, err) } if i > 0 { // FIXME(lukeshu): This is probably wrong, but // lots of my multi-device code is probably // wrong. if !sb.Data.Equal(sbs[0].Data) { return nil, fmt.Errorf("file %q superblock %v and file %q superblock %v disagree", sbs[0].File.Name(), 0, sb.File.Name(), sbi) } } } fs.cacheSuperblock = &sbs[0].Data return &sbs[0].Data, nil } func (fs *FS) ReInit(ctx context.Context) error { fs.LV.ClearMappings() for _, dev := range fs.LV.PhysicalVolumes() { sb, err := dev.Superblock() if err != nil { return fmt.Errorf("file %q: %w", dev.Name(), err) } if err := fs.initDev(*sb); err != nil { return fmt.Errorf("file %q: %w", dev.Name(), err) } } return fs.InitChunks(ctx) } func (fs *FS) initDev(sb btrfstree.Superblock) error { syschunks, err := sb.ParseSysChunkArray() if err != nil { return err } for _, chunk := range syschunks { for _, mapping := range chunk.Chunk.Mappings(chunk.Key) { if err := fs.LV.AddMapping(mapping); err != nil { return err } } } return nil } func (fs *FS) InitChunks(ctx context.Context) error { chunkTree, err := fs.ForrestLookup(ctx, btrfsprim.CHUNK_TREE_OBJECTID) if err != nil { return err } var errs derror.MultiError if err := chunkTree.TreeRange(ctx, func(item btrfstree.Item) bool { if item.Key.ItemType != btrfsitem.CHUNK_ITEM_KEY { return true } switch itemBody := item.Body.(type) { case *btrfsitem.Chunk: for _, mapping := range itemBody.Mappings(item.Key) { if err := fs.LV.AddMapping(mapping); err != nil { errs = append(errs, err) } } case *btrfsitem.Error: // do nothing default: // This is a panic because the item decoder should not emit CHUNK_ITEM items as // anything but btrfsitem.Chunk or btrfsitem.Error without this code also being // updated. panic(fmt.Errorf("should not happen: CHUNK_ITEM has unexpected item type: %T", itemBody)) } return true }); err != nil { errs = append(errs, err) } if len(errs) > 0 { return errs } return nil } func (fs *FS) Close() error { return fs.LV.Close() } var _ io.Closer = (*FS)(nil)