diff options
Diffstat (limited to 'lib/btrfs/btrfsvol')
-rw-r--r-- | lib/btrfs/btrfsvol/addr.go | 47 | ||||
-rw-r--r-- | lib/btrfs/btrfsvol/addr_test.go | 36 | ||||
-rw-r--r-- | lib/btrfs/btrfsvol/blockgroupflags.go | 51 | ||||
-rw-r--r-- | lib/btrfs/btrfsvol/chunk.go | 96 | ||||
-rw-r--r-- | lib/btrfs/btrfsvol/devext.go | 89 | ||||
-rw-r--r-- | lib/btrfs/btrfsvol/lvm.go | 355 |
6 files changed, 674 insertions, 0 deletions
diff --git a/lib/btrfs/btrfsvol/addr.go b/lib/btrfs/btrfsvol/addr.go new file mode 100644 index 0000000..6c67826 --- /dev/null +++ b/lib/btrfs/btrfsvol/addr.go @@ -0,0 +1,47 @@ +package btrfsvol + +import ( + "fmt" + + "git.lukeshu.com/btrfs-progs-ng/lib/util" +) + +type ( + PhysicalAddr int64 + LogicalAddr int64 + AddrDelta int64 +) + +func formatAddr(addr int64, f fmt.State, verb rune) { + switch verb { + case 'v', 's', 'q': + str := fmt.Sprintf("%#016x", addr) + fmt.Fprintf(f, util.FmtStateString(f, verb), str) + default: + fmt.Fprintf(f, util.FmtStateString(f, verb), addr) + } +} + +func (a PhysicalAddr) Format(f fmt.State, verb rune) { formatAddr(int64(a), f, verb) } +func (a LogicalAddr) Format(f fmt.State, verb rune) { formatAddr(int64(a), f, verb) } +func (d AddrDelta) Format(f fmt.State, verb rune) { formatAddr(int64(d), f, verb) } + +func (a PhysicalAddr) Sub(b PhysicalAddr) AddrDelta { return AddrDelta(a - b) } +func (a LogicalAddr) Sub(b LogicalAddr) AddrDelta { return AddrDelta(a - b) } + +func (a PhysicalAddr) Add(b AddrDelta) PhysicalAddr { return a + PhysicalAddr(b) } +func (a LogicalAddr) Add(b AddrDelta) LogicalAddr { return a + LogicalAddr(b) } + +type DeviceID uint64 + +type QualifiedPhysicalAddr struct { + Dev DeviceID + Addr PhysicalAddr +} + +func (a QualifiedPhysicalAddr) Cmp(b QualifiedPhysicalAddr) int { + if d := int(a.Dev - b.Dev); d != 0 { + return d + } + return int(a.Addr - b.Addr) +} diff --git a/lib/btrfs/btrfsvol/addr_test.go b/lib/btrfs/btrfsvol/addr_test.go new file mode 100644 index 0000000..aae6378 --- /dev/null +++ b/lib/btrfs/btrfsvol/addr_test.go @@ -0,0 +1,36 @@ +package btrfsvol_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol" +) + +func TestAddrFormat(t *testing.T) { + t.Parallel() + type TestCase struct { + InputAddr btrfsvol.LogicalAddr + InputFmt string + Output string + } + addr := btrfsvol.LogicalAddr(0x3a41678000) + testcases := map[string]TestCase{ + "v": {InputAddr: addr, InputFmt: "%v", Output: "0x0000003a41678000"}, + "s": {InputAddr: addr, InputFmt: "%s", Output: "0x0000003a41678000"}, + "q": {InputAddr: addr, InputFmt: "%q", Output: `"0x0000003a41678000"`}, + "x": {InputAddr: addr, InputFmt: "%x", Output: "3a41678000"}, + "d": {InputAddr: addr, InputFmt: "%d", Output: "250205405184"}, + "neg": {InputAddr: -1, InputFmt: "%v", Output: "-0x000000000000001"}, + } + for tcName, tc := range testcases { + tc := tc + t.Run(tcName, func(t *testing.T) { + t.Parallel() + actual := fmt.Sprintf(tc.InputFmt, tc.InputAddr) + assert.Equal(t, tc.Output, actual) + }) + } +} diff --git a/lib/btrfs/btrfsvol/blockgroupflags.go b/lib/btrfs/btrfsvol/blockgroupflags.go new file mode 100644 index 0000000..1a194d7 --- /dev/null +++ b/lib/btrfs/btrfsvol/blockgroupflags.go @@ -0,0 +1,51 @@ +package btrfsvol + +import ( + "git.lukeshu.com/btrfs-progs-ng/lib/util" +) + +type BlockGroupFlags uint64 + +const ( + BLOCK_GROUP_DATA = BlockGroupFlags(1 << iota) + BLOCK_GROUP_SYSTEM + BLOCK_GROUP_METADATA + BLOCK_GROUP_RAID0 + BLOCK_GROUP_RAID1 + BLOCK_GROUP_DUP + BLOCK_GROUP_RAID10 + BLOCK_GROUP_RAID5 + BLOCK_GROUP_RAID6 + BLOCK_GROUP_RAID1C3 + BLOCK_GROUP_RAID1C4 + + BLOCK_GROUP_RAID_MASK = (BLOCK_GROUP_RAID1 | BLOCK_GROUP_DUP | BLOCK_GROUP_RAID10 | BLOCK_GROUP_RAID5 | BLOCK_GROUP_RAID6 | BLOCK_GROUP_RAID1C3 | BLOCK_GROUP_RAID1C4) +) + +var blockGroupFlagNames = []string{ + "DATA", + "SYSTEM", + "METADATA", + + "RAID0", + "RAID1", + "DUP", + "RAID10", + "RAID5", + "RAID6", + "RAID1C3", + "RAID1C4", +} + +func (f BlockGroupFlags) Has(req BlockGroupFlags) bool { return f&req == req } +func (f BlockGroupFlags) String() string { + ret := util.BitfieldString(f, blockGroupFlagNames, util.HexNone) + if f&BLOCK_GROUP_RAID_MASK == 0 { + if ret == "" { + ret = "single" + } else { + ret += "|single" + } + } + return ret +} diff --git a/lib/btrfs/btrfsvol/chunk.go b/lib/btrfs/btrfsvol/chunk.go new file mode 100644 index 0000000..6aea483 --- /dev/null +++ b/lib/btrfs/btrfsvol/chunk.go @@ -0,0 +1,96 @@ +package btrfsvol + +import ( + "fmt" + "sort" + + "git.lukeshu.com/btrfs-progs-ng/lib/util" +) + +// logical => []physical +type chunkMapping struct { + LAddr LogicalAddr + PAddrs []QualifiedPhysicalAddr + Size AddrDelta + SizeLocked bool + Flags *BlockGroupFlags +} + +type ChunkMapping = chunkMapping + +// return -1 if 'a' is wholly to the left of 'b' +// return 0 if there is some overlap between 'a' and 'b' +// return 1 if 'a is wholly to the right of 'b' +func (a chunkMapping) cmpRange(b chunkMapping) int { + switch { + case a.LAddr.Add(a.Size) <= b.LAddr: + // 'a' is wholly to the left of 'b'. + return -1 + case b.LAddr.Add(b.Size) <= a.LAddr: + // 'a' is wholly to the right of 'b'. + return 1 + default: + // There is some overlap. + return 0 + } +} + +func (a chunkMapping) union(rest ...chunkMapping) (chunkMapping, error) { + // sanity check + for _, chunk := range rest { + if a.cmpRange(chunk) != 0 { + return chunkMapping{}, fmt.Errorf("chunks don't overlap") + } + } + chunks := append([]chunkMapping{a}, rest...) + // figure out the logical range (.LAddr and .Size) + beg := chunks[0].LAddr + end := chunks[0].LAddr.Add(chunks[0].Size) + for _, chunk := range chunks { + beg = util.Min(beg, chunk.LAddr) + end = util.Max(end, chunk.LAddr.Add(chunk.Size)) + } + ret := chunkMapping{ + LAddr: beg, + Size: end.Sub(beg), + } + for _, chunk := range chunks { + if chunk.SizeLocked { + ret.SizeLocked = true + if ret.Size != chunk.Size { + return chunkMapping{}, fmt.Errorf("member chunk has locked size=%v, but union would have size=%v", + chunk.Size, ret.Size) + } + } + } + // figure out the physical stripes (.PAddrs) + paddrs := make(map[QualifiedPhysicalAddr]struct{}) + for _, chunk := range chunks { + offsetWithinRet := chunk.LAddr.Sub(ret.LAddr) + for _, stripe := range chunk.PAddrs { + paddrs[QualifiedPhysicalAddr{ + Dev: stripe.Dev, + Addr: stripe.Addr.Add(-offsetWithinRet), + }] = struct{}{} + } + } + ret.PAddrs = util.MapKeys(paddrs) + sort.Slice(ret.PAddrs, func(i, j int) bool { + return ret.PAddrs[i].Cmp(ret.PAddrs[j]) < 0 + }) + // figure out the flags (.Flags) + for _, chunk := range chunks { + if chunk.Flags == nil { + continue + } + if ret.Flags == nil { + val := *chunk.Flags + ret.Flags = &val + } + if *ret.Flags != *chunk.Flags { + return ret, fmt.Errorf("mismatch flags: %v != %v", *ret.Flags, *chunk.Flags) + } + } + // done + return ret, nil +} diff --git a/lib/btrfs/btrfsvol/devext.go b/lib/btrfs/btrfsvol/devext.go new file mode 100644 index 0000000..43c6255 --- /dev/null +++ b/lib/btrfs/btrfsvol/devext.go @@ -0,0 +1,89 @@ +package btrfsvol + +import ( + "fmt" + + "git.lukeshu.com/btrfs-progs-ng/lib/util" +) + +// physical => logical +type devextMapping struct { + PAddr PhysicalAddr + LAddr LogicalAddr + Size AddrDelta + SizeLocked bool + Flags *BlockGroupFlags +} + +// return -1 if 'a' is wholly to the left of 'b' +// return 0 if there is some overlap between 'a' and 'b' +// return 1 if 'a is wholly to the right of 'b' +func (a devextMapping) cmpRange(b devextMapping) int { + switch { + case a.PAddr.Add(a.Size) <= b.PAddr: + // 'a' is wholly to the left of 'b'. + return -1 + case b.PAddr.Add(b.Size) <= a.PAddr: + // 'a' is wholly to the right of 'b'. + return 1 + default: + // There is some overlap. + return 0 + } +} + +func (a devextMapping) union(rest ...devextMapping) (devextMapping, error) { + // sanity check + for _, ext := range rest { + if a.cmpRange(ext) != 0 { + return devextMapping{}, fmt.Errorf("devexts don't overlap") + } + } + exts := append([]devextMapping{a}, rest...) + // figure out the physical range (.PAddr and .Size) + beg := exts[0].PAddr + end := beg.Add(exts[0].Size) + for _, ext := range exts { + beg = util.Min(beg, ext.PAddr) + end = util.Max(end, ext.PAddr.Add(ext.Size)) + } + ret := devextMapping{ + PAddr: beg, + Size: end.Sub(beg), + } + for _, ext := range exts { + if ext.SizeLocked { + ret.SizeLocked = true + if ret.Size != ext.Size { + return devextMapping{}, fmt.Errorf("member devext has locked size=%v, but union would have size=%v", + ext.Size, ret.Size) + } + } + } + // figure out the logical range (.LAddr) + first := true + for _, ext := range exts { + offsetWithinRet := ext.PAddr.Sub(ret.PAddr) + laddr := ext.LAddr.Add(-offsetWithinRet) + if first { + ret.LAddr = laddr + } else if laddr != ret.LAddr { + return ret, fmt.Errorf("devexts don't agree on laddr: %v != %v", ret.LAddr, laddr) + } + } + // figure out the flags (.Flags) + for _, ext := range exts { + if ext.Flags == nil { + continue + } + if ret.Flags == nil { + val := *ext.Flags + ret.Flags = &val + } + if *ret.Flags != *ext.Flags { + return ret, fmt.Errorf("mismatch flags: %v != %v", *ret.Flags, *ext.Flags) + } + } + // done + return ret, nil +} diff --git a/lib/btrfs/btrfsvol/lvm.go b/lib/btrfs/btrfsvol/lvm.go new file mode 100644 index 0000000..3b9ccf6 --- /dev/null +++ b/lib/btrfs/btrfsvol/lvm.go @@ -0,0 +1,355 @@ +package btrfsvol + +import ( + "bytes" + "fmt" + "os" + "reflect" + + "git.lukeshu.com/btrfs-progs-ng/lib/rbtree" + "git.lukeshu.com/btrfs-progs-ng/lib/util" +) + +type LogicalVolume[PhysicalVolume util.File[PhysicalAddr]] struct { + name string + + id2pv map[DeviceID]PhysicalVolume + + logical2physical *rbtree.Tree[LogicalAddr, chunkMapping] + physical2logical map[DeviceID]*rbtree.Tree[PhysicalAddr, devextMapping] +} + +var _ util.File[LogicalAddr] = (*LogicalVolume[util.File[PhysicalAddr]])(nil) + +func (lv *LogicalVolume[PhysicalVolume]) init() { + if lv.id2pv == nil { + lv.id2pv = make(map[DeviceID]PhysicalVolume) + } + if lv.logical2physical == nil { + lv.logical2physical = &rbtree.Tree[LogicalAddr, chunkMapping]{ + KeyFn: func(chunk chunkMapping) LogicalAddr { + return chunk.LAddr + }, + } + } + if lv.physical2logical == nil { + lv.physical2logical = make(map[DeviceID]*rbtree.Tree[PhysicalAddr, devextMapping], len(lv.id2pv)) + } + for devid := range lv.id2pv { + if _, ok := lv.physical2logical[devid]; !ok { + lv.physical2logical[devid] = &rbtree.Tree[PhysicalAddr, devextMapping]{ + KeyFn: func(ext devextMapping) PhysicalAddr { + return ext.PAddr + }, + } + } + } +} + +func (lv *LogicalVolume[PhysicalVolume]) SetName(name string) { + lv.name = name +} + +func (lv *LogicalVolume[PhysicalVolume]) Name() string { + return lv.name +} + +func (lv *LogicalVolume[PhysicalVolume]) Size() (LogicalAddr, error) { + lv.init() + lastChunk := lv.logical2physical.Max() + if lastChunk == nil { + return 0, nil + } + return lastChunk.Value.LAddr.Add(lastChunk.Value.Size), nil +} + +func (lv *LogicalVolume[PhysicalVolume]) AddPhysicalVolume(id DeviceID, dev PhysicalVolume) error { + lv.init() + if other, exists := lv.id2pv[id]; exists { + return fmt.Errorf("(%p).AddPhysicalVolume: cannot add physical volume %q: already have physical volume %q with id=%v", + lv, dev.Name(), other.Name(), id) + } + lv.id2pv[id] = dev + lv.physical2logical[id] = &rbtree.Tree[PhysicalAddr, devextMapping]{ + KeyFn: func(ext devextMapping) PhysicalAddr { + return ext.PAddr + }, + } + return nil +} + +func (lv *LogicalVolume[PhysicalVolume]) PhysicalVolumes() map[DeviceID]PhysicalVolume { + dup := make(map[DeviceID]PhysicalVolume, len(lv.id2pv)) + for k, v := range lv.id2pv { + dup[k] = v + } + return dup +} + +func (lv *LogicalVolume[PhysicalVolume]) ClearMappings() { + lv.logical2physical = nil + lv.physical2logical = nil +} + +type Mapping struct { + LAddr LogicalAddr + PAddr QualifiedPhysicalAddr + Size AddrDelta + SizeLocked bool + Flags *BlockGroupFlags +} + +func (lv *LogicalVolume[PhysicalVolume]) AddMapping(m Mapping) error { + lv.init() + // sanity check + if _, haveDev := lv.id2pv[m.PAddr.Dev]; !haveDev { + return fmt.Errorf("(%p).AddMapping: do not have a physical volume with id=%v", + lv, m.PAddr.Dev) + } + + // logical2physical + newChunk := chunkMapping{ + LAddr: m.LAddr, + PAddrs: []QualifiedPhysicalAddr{m.PAddr}, + Size: m.Size, + SizeLocked: m.SizeLocked, + Flags: m.Flags, + } + logicalOverlaps := lv.logical2physical.SearchRange(newChunk.cmpRange) + var err error + newChunk, err = newChunk.union(logicalOverlaps...) + if err != nil { + return fmt.Errorf("(%p).AddMapping: %w", lv, err) + } + + // physical2logical + newExt := devextMapping{ + PAddr: m.PAddr.Addr, + LAddr: m.LAddr, + Size: m.Size, + SizeLocked: m.SizeLocked, + Flags: m.Flags, + } + physicalOverlaps := lv.physical2logical[m.PAddr.Dev].SearchRange(newExt.cmpRange) + newExt, err = newExt.union(physicalOverlaps...) + if err != nil { + return fmt.Errorf("(%p).AddMapping: %w", lv, err) + } + + // optimize + if len(logicalOverlaps) == 1 && reflect.DeepEqual(newChunk, logicalOverlaps[0]) && + len(physicalOverlaps) == 1 && reflect.DeepEqual(newExt, physicalOverlaps[0]) { + return nil + } + + // logical2physical + for _, chunk := range logicalOverlaps { + lv.logical2physical.Delete(chunk.LAddr) + } + lv.logical2physical.Insert(newChunk) + + // physical2logical + for _, ext := range physicalOverlaps { + lv.physical2logical[m.PAddr.Dev].Delete(ext.PAddr) + } + lv.physical2logical[m.PAddr.Dev].Insert(newExt) + + // sanity check + // + // This is in-theory unnescessary, but that assumes that I + // made no mistakes in my algorithm above. + if os.Getenv("PARANOID") != "" { + if err := lv.fsck(); err != nil { + return err + } + } + + // done + return nil +} + +func (lv *LogicalVolume[PhysicalVolume]) fsck() error { + physical2logical := make(map[DeviceID]*rbtree.Tree[PhysicalAddr, devextMapping]) + if err := lv.logical2physical.Walk(func(node *rbtree.Node[chunkMapping]) error { + chunk := node.Value + for _, stripe := range chunk.PAddrs { + if _, devOK := lv.id2pv[stripe.Dev]; !devOK { + return fmt.Errorf("(%p).fsck: chunk references physical volume %v which does not exist", + lv, stripe.Dev) + } + if _, exists := physical2logical[stripe.Dev]; !exists { + physical2logical[stripe.Dev] = &rbtree.Tree[PhysicalAddr, devextMapping]{ + KeyFn: func(ext devextMapping) PhysicalAddr { + return ext.PAddr + }, + } + } + physical2logical[stripe.Dev].Insert(devextMapping{ + PAddr: stripe.Addr, + LAddr: chunk.LAddr, + Size: chunk.Size, + Flags: chunk.Flags, + }) + } + return nil + }); err != nil { + return err + } + + if len(lv.physical2logical) != len(physical2logical) { + return fmt.Errorf("(%p).fsck: skew between chunk tree and devext tree", + lv) + } + for devid := range lv.physical2logical { + if !lv.physical2logical[devid].Equal(physical2logical[devid]) { + return fmt.Errorf("(%p).fsck: skew between chunk tree and devext tree", + lv) + } + } + + return nil +} + +func (lv *LogicalVolume[PhysicalVolume]) Mappings() []Mapping { + var ret []Mapping + _ = lv.logical2physical.Walk(func(node *rbtree.Node[chunkMapping]) error { + chunk := node.Value + var flags *BlockGroupFlags + if chunk.Flags != nil { + val := *chunk.Flags + flags = &val + } + for _, slice := range chunk.PAddrs { + ret = append(ret, Mapping{ + LAddr: chunk.LAddr, + PAddr: slice, + Size: chunk.Size, + Flags: flags, + }) + } + return nil + }) + return ret +} + +func (lv *LogicalVolume[PhysicalVolume]) Resolve(laddr LogicalAddr) (paddrs map[QualifiedPhysicalAddr]struct{}, maxlen AddrDelta) { + node := lv.logical2physical.Search(func(chunk chunkMapping) int { + return chunkMapping{LAddr: laddr, Size: 1}.cmpRange(chunk) + }) + if node == nil { + return nil, 0 + } + + chunk := node.Value + + offsetWithinChunk := laddr.Sub(chunk.LAddr) + paddrs = make(map[QualifiedPhysicalAddr]struct{}) + maxlen = chunk.Size - offsetWithinChunk + for _, stripe := range chunk.PAddrs { + paddrs[QualifiedPhysicalAddr{ + Dev: stripe.Dev, + Addr: stripe.Addr.Add(offsetWithinChunk), + }] = struct{}{} + } + + return paddrs, maxlen +} + +func (lv *LogicalVolume[PhysicalVolume]) ResolveAny(laddr LogicalAddr, size AddrDelta) (LogicalAddr, QualifiedPhysicalAddr) { + node := lv.logical2physical.Search(func(chunk chunkMapping) int { + return chunkMapping{LAddr: laddr, Size: size}.cmpRange(chunk) + }) + if node == nil { + return -1, QualifiedPhysicalAddr{0, -1} + } + return node.Value.LAddr, node.Value.PAddrs[0] +} + +func (lv *LogicalVolume[PhysicalVolume]) UnResolve(paddr QualifiedPhysicalAddr) LogicalAddr { + node := lv.physical2logical[paddr.Dev].Search(func(ext devextMapping) int { + return devextMapping{PAddr: paddr.Addr, Size: 1}.cmpRange(ext) + }) + if node == nil { + return -1 + } + + ext := node.Value + + offsetWithinExt := paddr.Addr.Sub(ext.PAddr) + return ext.LAddr.Add(offsetWithinExt) +} + +func (lv *LogicalVolume[PhysicalVolume]) ReadAt(dat []byte, laddr LogicalAddr) (int, error) { + done := 0 + for done < len(dat) { + n, err := lv.maybeShortReadAt(dat[done:], laddr+LogicalAddr(done)) + done += n + if err != nil { + return done, err + } + } + return done, nil +} + +func (lv *LogicalVolume[PhysicalVolume]) maybeShortReadAt(dat []byte, laddr LogicalAddr) (int, error) { + paddrs, maxlen := lv.Resolve(laddr) + if len(paddrs) == 0 { + return 0, fmt.Errorf("read: could not map logical address %v", laddr) + } + if AddrDelta(len(dat)) > maxlen { + dat = dat[:maxlen] + } + + buf := make([]byte, len(dat)) + first := true + for paddr := range paddrs { + dev, ok := lv.id2pv[paddr.Dev] + if !ok { + return 0, fmt.Errorf("device=%v does not exist", paddr.Dev) + } + if _, err := dev.ReadAt(buf, paddr.Addr); err != nil { + return 0, fmt.Errorf("read device=%v paddr=%v: %w", paddr.Dev, paddr.Addr, err) + } + if first { + copy(dat, buf) + } else { + if !bytes.Equal(dat, buf) { + return 0, fmt.Errorf("inconsistent stripes at laddr=%v len=%v", laddr, len(dat)) + } + } + } + return len(dat), nil +} + +func (lv *LogicalVolume[PhysicalVolume]) WriteAt(dat []byte, laddr LogicalAddr) (int, error) { + done := 0 + for done < len(dat) { + n, err := lv.maybeShortWriteAt(dat[done:], laddr+LogicalAddr(done)) + done += n + if err != nil { + return done, err + } + } + return done, nil +} + +func (lv *LogicalVolume[PhysicalVolume]) maybeShortWriteAt(dat []byte, laddr LogicalAddr) (int, error) { + paddrs, maxlen := lv.Resolve(laddr) + if len(paddrs) == 0 { + return 0, fmt.Errorf("write: could not map logical address %v", laddr) + } + if AddrDelta(len(dat)) > maxlen { + dat = dat[:maxlen] + } + + for paddr := range paddrs { + dev, ok := lv.id2pv[paddr.Dev] + if !ok { + return 0, fmt.Errorf("device=%v does not exist", paddr.Dev) + } + if _, err := dev.WriteAt(dat, paddr.Addr); err != nil { + return 0, fmt.Errorf("write device=%v paddr=%v: %w", paddr.Dev, paddr.Addr, err) + } + } + return len(dat), nil +} |