diff options
Diffstat (limited to 'pkg/btrfs/io2_fs.go')
-rw-r--r-- | pkg/btrfs/io2_fs.go | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/pkg/btrfs/io2_fs.go b/pkg/btrfs/io2_fs.go new file mode 100644 index 0000000..9b1e717 --- /dev/null +++ b/pkg/btrfs/io2_fs.go @@ -0,0 +1,274 @@ +package btrfs + +import ( + "bytes" + "fmt" + "reflect" + + "lukeshu.com/btrfs-tools/pkg/binstruct" + "lukeshu.com/btrfs-tools/pkg/btrfs/btrfsitem" + . "lukeshu.com/btrfs-tools/pkg/btrfs/btrfstyp" + "lukeshu.com/btrfs-tools/pkg/util" +) + +type FS struct { + Devices []*Device + + initErr error + uuid2dev map[UUID]*Device + chunks []SysChunk +} + +func (fs *FS) Name() string { + sb, err := fs.Superblock() + if err != nil { + return fmt.Sprintf("fs_uuid=%s", "(unreadable)") + } + return fmt.Sprintf("fs_uuid=%s", sb.Data.FSUUID) +} + +func (fs *FS) Size() (LogicalAddr, error) { + var ret LogicalAddr + for _, dev := range fs.Devices { + sz, err := dev.Size() + if err != nil { + return 0, fmt.Errorf("file %q: %w", dev.Name(), err) + } + ret += LogicalAddr(sz) + } + return ret, nil +} + +func (fs *FS) Superblocks() ([]util.Ref[PhysicalAddr, Superblock], error) { + var ret []util.Ref[PhysicalAddr, Superblock] + for _, dev := range fs.Devices { + sbs, err := dev.Superblocks() + if err != nil { + return nil, fmt.Errorf("file %q: %w", dev.Name(), err) + } + ret = append(ret, sbs...) + } + return ret, nil +} + +func (fs *FS) Superblock() (ret util.Ref[PhysicalAddr, Superblock], err error) { + sbs, err := fs.Superblocks() + if err != nil { + return ret, err + } + + 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 ret, fmt.Errorf("file %q superblock %d: %w", sb.File.Name(), sbi, err) + } + if i > 0 { + // This is probably wrong, but lots of my + // multi-device code is probably wrong. + if !sb.Data.Equal(sbs[0].Data) { + return ret, fmt.Errorf("file %q superblock %d and file %q superblock %d disagree", + sbs[0].File.Name(), 0, + sb.File.Name(), sbi) + } + } + } + + return sbs[0], nil +} + +func (fs *FS) Init() error { + if fs.uuid2dev != nil { + return fs.initErr + } + fs.uuid2dev = make(map[UUID]*Device, len(fs.Devices)) + for _, dev := range fs.Devices { + sbs, err := dev.Superblocks() + if err != nil { + fs.initErr = fmt.Errorf("file %q: %w", dev.Name(), err) + return fs.initErr + } + + a := sbs[0].Data + a.Checksum = CSum{} + a.Self = 0 + for i, sb := range sbs[1:] { + b := sb.Data + b.Checksum = CSum{} + b.Self = 0 + if !reflect.DeepEqual(a, b) { + fs.initErr = fmt.Errorf("file %q: superblock %d disagrees with superblock 0", + dev.Name(), i+1) + return fs.initErr + } + } + sb := sbs[0] + if other, exists := fs.uuid2dev[sb.Data.DevItem.DevUUID]; exists { + fs.initErr = fmt.Errorf("file %q and file %q have the same device ID: %v", + other.Name(), dev.Name(), sb.Data.DevItem.DevUUID) + return fs.initErr + } + fs.uuid2dev[sb.Data.DevItem.DevUUID] = dev + syschunks, err := sb.Data.ParseSysChunkArray() + if err != nil { + fs.initErr = fmt.Errorf("file %q: %w", dev.Name(), err) + return fs.initErr + } + for _, chunk := range syschunks { + fs.chunks = append(fs.chunks, chunk) + } + if err := fs.WalkTree(sb.Data.ChunkTree, func(key Key, dat []byte) error { + if key.ItemType != btrfsitem.CHUNK_ITEM_KEY { + return nil + } + pair := SysChunk{ + Key: key, + } + if _, err := binstruct.Unmarshal(dat, &pair.Chunk); err != nil { + return err + } + fs.chunks = append(fs.chunks, pair) + return nil + }); err != nil { + fs.initErr = err + return fs.initErr + } + } + return nil +} + +func (fs *FS) ReadAt(dat []byte, laddr LogicalAddr) (int, error) { + done := 0 + for done < len(dat) { + n, err := fs.maybeShortReadAt(dat[done:], laddr+LogicalAddr(done)) + done += n + if err != nil { + return done, err + } + } + return done, nil +} + +func (fs *FS) maybeShortReadAt(dat []byte, laddr LogicalAddr) (int, error) { + type physicalAddr struct { + Dev UUID + Addr PhysicalAddr + } + + paddrs := make(map[physicalAddr]struct{}) + + for _, chunk := range fs.chunks { + if chunk.Offset <= uint64(laddr) && uint64(laddr) < chunk.Offset+uint64(chunk.Chunk.Size) { + offsetWithinChunk := uint64(laddr) - chunk.Offset + if offsetWithinChunk+uint64(len(dat)) > chunk.Chunk.Size { + dat = dat[:chunk.Chunk.Size-offsetWithinChunk] + } + for _, stripe := range chunk.Chunk.Stripes { + paddrs[physicalAddr{ + Dev: stripe.DeviceUUID, + Addr: PhysicalAddr(stripe.Offset + offsetWithinChunk), + }] = struct{}{} + } + } + } + + if len(paddrs) == 0 { + return 0, fmt.Errorf("could not map logical address %v", laddr) + } + + buf := make([]byte, len(dat)) + first := true + for paddr := range paddrs { + dev, ok := fs.uuid2dev[paddr.Dev] + if !ok { + return 0, fmt.Errorf("device=%s does not exist", paddr.Dev) + } + if _, err := dev.ReadAt(buf, paddr.Addr); err != nil { + return 0, fmt.Errorf("read device=%s 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=%d", laddr, len(dat)) + } + } + } + return len(dat), nil +} + +func (fs *FS) ReadNode(addr LogicalAddr) (util.Ref[LogicalAddr, Node], error) { + var ret util.Ref[LogicalAddr, Node] + + sb, err := fs.Superblock() + if err != nil { + return ret, err + } + + nodeBuf := make([]byte, sb.Data.NodeSize) + if _, err := fs.ReadAt(nodeBuf, addr); err != nil { + return ret, err + } + + var node Node + node.Size = sb.Data.NodeSize + + if _, err := node.UnmarshalBinary(nodeBuf); err != nil { + return ret, fmt.Errorf("node@%d: %w", addr, err) + } + + // sanity checking + + if !node.Head.MetadataUUID.Equal(sb.Data.EffectiveMetadataUUID()) { + return ret, fmt.Errorf("node@%d: does not look like a node", addr) + } + + if node.Head.Addr != addr { + return ret, fmt.Errorf("node@%d: read from laddr=%d but claims to be at laddr=%d", + addr, addr, node.Head.Addr) + } + + stored := node.Head.Checksum + calced := CRC32c(nodeBuf[binstruct.StaticSize(CSum{}):]) + if !calced.Equal(stored) { + return ret, fmt.Errorf("node@%d: checksum mismatch: stored=%s calculated=%s", + addr, stored, calced) + } + + // return + + return util.Ref[LogicalAddr, Node]{ + File: fs, + Addr: addr, + Data: node, + }, nil +} + +func (fs *FS) WalkTree(nodeAddr LogicalAddr, fn func(Key, []byte) error) error { + if nodeAddr == 0 { + return nil + } + node, err := fs.ReadNode(nodeAddr) + if err != nil { + return err + } + for _, item := range node.Data.BodyInternal { + // fn(item.Data.Key, TODO) + if err := fs.WalkTree(item.BlockPtr, fn); err != nil { + return err + } + } + for _, item := range node.Data.BodyLeaf { + if err := fn(item.Head.Key, item.Body); err != nil { + return err + } + } + return nil +} |