diff options
-rw-r--r-- | cmd/btrfs-dbg/main.go | 16 | ||||
-rw-r--r-- | cmd/btrfs-dump-tree/main.go | 67 | ||||
-rw-r--r-- | notes.org | 4 | ||||
-rw-r--r-- | pkg/btrfs/fsck.go | 52 | ||||
-rw-r--r-- | pkg/btrfs/image.go | 116 | ||||
-rw-r--r-- | pkg/btrfs/io_device.go | 69 | ||||
-rw-r--r-- | pkg/btrfs/io_fs.go | 123 | ||||
-rw-r--r-- | pkg/btrfs/io_ref.go | 23 | ||||
-rw-r--r-- | pkg/btrfs/types_bitfields.go (renamed from pkg/btrfs/bitfields.go) | 0 | ||||
-rw-r--r-- | pkg/btrfs/types_item.go | 188 | ||||
-rw-r--r-- | pkg/btrfs/types_objid.go (renamed from pkg/btrfs/objid.go) | 5 | ||||
-rw-r--r-- | pkg/btrfs/types_structs.go (renamed from pkg/btrfs/structs.go) | 47 | ||||
-rw-r--r-- | pkg/btrfs/types_uuid.go (renamed from pkg/btrfs/uuid.go) | 0 |
13 files changed, 575 insertions, 135 deletions
diff --git a/cmd/btrfs-dbg/main.go b/cmd/btrfs-dbg/main.go index 6d60a73..1b3b993 100644 --- a/cmd/btrfs-dbg/main.go +++ b/cmd/btrfs-dbg/main.go @@ -27,14 +27,18 @@ func Main(imgfilename string) (err error) { if err != nil { return err } - img := &btrfs.Img{ - File: fh, - } defer func() { - maybeSetErr(img.Close()) + maybeSetErr(fh.Close()) }() + fs := &btrfs.FS{ + Devices: []*btrfs.Device{ + { + File: fh, + }, + }, + } - superblocks, err := img.Superblocks() + superblocks, err := fs.Devices[0].Superblocks() if err != nil { return err } @@ -55,7 +59,7 @@ func Main(imgfilename string) (err error) { } spew.Dump(syschunks) - if err := img.ScanForNodes(superblocks[0].Data); err != nil { + if err := btrfs.ScanForNodes(fs.Devices[0], superblocks[0].Data); err != nil { return err } diff --git a/cmd/btrfs-dump-tree/main.go b/cmd/btrfs-dump-tree/main.go new file mode 100644 index 0000000..1b3b993 --- /dev/null +++ b/cmd/btrfs-dump-tree/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "os" + + "github.com/davecgh/go-spew/spew" + + "lukeshu.com/btrfs-tools/pkg/btrfs" +) + +func main() { + if err := Main(os.Args[1]); err != nil { + fmt.Fprintf(os.Stderr, "%s: error: %v\n", os.Args[0], err) + os.Exit(1) + } +} + +func Main(imgfilename string) (err error) { + maybeSetErr := func(_err error) { + if _err != nil && err == nil { + err = _err + } + } + + fh, err := os.Open(imgfilename) + if err != nil { + return err + } + defer func() { + maybeSetErr(fh.Close()) + }() + fs := &btrfs.FS{ + Devices: []*btrfs.Device{ + { + File: fh, + }, + }, + } + + superblocks, err := fs.Devices[0].Superblocks() + if err != nil { + return err + } + + spew := spew.NewDefaultConfig() + spew.DisablePointerAddresses = true + + sum, err := superblocks[0].Data.CalculateChecksum() + if err != nil { + return err + } + fmt.Printf("superblock checksum: %x\n", sum) + spew.Dump(superblocks[0].Data) + + syschunks, err := superblocks[0].Data.ParseSysChunkArray() + if err != nil { + return err + } + spew.Dump(syschunks) + + if err := btrfs.ScanForNodes(fs.Devices[0], superblocks[0].Data); err != nil { + return err + } + + return nil +} diff --git a/notes.org b/notes.org new file mode 100644 index 0000000..ba4841e --- /dev/null +++ b/notes.org @@ -0,0 +1,4 @@ + - [X] look at `btrfs inspect-internal dump-tree` on a good FS + - [ ] duplicate it + - [ ] draw a diagram of what the struct relations are + diff --git a/pkg/btrfs/fsck.go b/pkg/btrfs/fsck.go new file mode 100644 index 0000000..d31735e --- /dev/null +++ b/pkg/btrfs/fsck.go @@ -0,0 +1,52 @@ +package btrfs + +import ( + "fmt" + + "lukeshu.com/btrfs-tools/pkg/binstruct" +) + +// ScanForNodes mimics btrfs-progs +// cmds/rescue-chunk-recover.c:scan_one_device(), except it doesn't do +// anything but log when it finds a node. +func ScanForNodes(dev *Device, sb Superblock) error { + devSize, err := dev.Size() + if err != nil { + return err + } + + if sb.NodeSize < sb.SectorSize { + return fmt.Errorf("node_size(%d) < sector_size(%d)", + sb.NodeSize, sb.SectorSize) + } + + nodeBuf := make([]byte, sb.NodeSize) + for pos := int64(0); pos+int64(sb.SectorSize) < devSize; pos += int64(sb.SectorSize) { + if inSlice(pos, superblockAddrs) { + fmt.Printf("sector@%d is a superblock\n", pos) + continue + } + if _, err := dev.ReadAt(nodeBuf, pos); err != nil { + return fmt.Errorf("sector@%d: %w", pos, err) + } + var nodeHeader NodeHeader + if err := binstruct.Unmarshal(nodeBuf, &nodeHeader); err != nil { + return fmt.Errorf("sector@%d: %w", pos, err) + } + if !nodeHeader.MetadataUUID.Equal(sb.EffectiveMetadataUUID()) { + //fmt.Printf("sector@%d does not look like a node\n", pos) + continue + } + if !nodeHeader.Checksum.Equal(CRC32c(nodeBuf[0x20:])) { + fmt.Printf("sector@%d looks like a node but is corrupt (checksum doesn't match)\n", pos) + continue + } + + fmt.Printf("node@%d: physical_addr=0x%0X logical_addr=0x%0X generation=%d owner=%v level=%d\n", + pos, pos, nodeHeader.Addr, nodeHeader.Generation, nodeHeader.Owner, nodeHeader.Level) + + pos += int64(sb.NodeSize) - int64(sb.SectorSize) + } + + return nil +} diff --git a/pkg/btrfs/image.go b/pkg/btrfs/image.go deleted file mode 100644 index ec49940..0000000 --- a/pkg/btrfs/image.go +++ /dev/null @@ -1,116 +0,0 @@ -package btrfs - -import ( - "fmt" - "os" - - "lukeshu.com/btrfs-tools/pkg/binstruct" -) - -type Img struct { - *os.File -} - -func (img *Img) Size() (int64, error) { - fi, err := img.Stat() - if err != nil { - return 0, err - } - return fi.Size(), nil -} - -type Ref[T any] struct { - img *Img - addr int64 - Data T -} - -func (r *Ref[T]) Read() error { - size, err := binstruct.Size(r.Data) - if err != nil { - return err - } - buf := make([]byte, size) - if _, err := r.img.ReadAt(buf, r.addr); err != nil { - return err - } - return binstruct.Unmarshal(buf, &r.Data) -} - -var superblockAddrs = []int64{ - 0x00_0001_0000, // 64KiB - 0x00_0400_0000, // 64MiB - 0x40_0000_0000, // 256GiB -} - -func (img *Img) Superblocks() ([]Ref[Superblock], error) { - const superblockSize = 0x1000 - - sz, err := img.Size() - if err != nil { - return nil, err - } - - var ret []Ref[Superblock] - for i, addr := range superblockAddrs { - if addr+superblockSize <= sz { - superblock := Ref[Superblock]{ - img: img, - addr: addr, - } - if err := superblock.Read(); err != nil { - return nil, fmt.Errorf("superblock %d: %w", i, err) - } - ret = append(ret, superblock) - } - } - if len(ret) == 0 { - return nil, fmt.Errorf("no superblocks") - } - return ret, nil -} - -// ScanForNodes mimics btrfs-progs -// cmds/rescue-chunk-recover.c:scan_one_device(), except it doesn't do -// anything but log when it finds a node. -func (img *Img) ScanForNodes(sb Superblock) error { - devSize, err := img.Size() - if err != nil { - return err - } - - if sb.NodeSize < sb.SectorSize { - return fmt.Errorf("node_size(%d) < sector_size(%d)", - sb.NodeSize, sb.SectorSize) - } - - nodeBuf := make([]byte, sb.NodeSize) - for pos := int64(0); pos+int64(sb.SectorSize) <= devSize; pos += int64(sb.SectorSize) { - if inSlice(pos, superblockAddrs) { - fmt.Printf("sector@%d is a superblock\n", pos) - continue - } - if _, err := img.ReadAt(nodeBuf, pos); err != nil { - return fmt.Errorf("sector@%d: %w", pos, err) - } - var nodeHeader NodeHeader - if err := binstruct.Unmarshal(nodeBuf, &nodeHeader); err != nil { - return fmt.Errorf("sector@%d: %w", pos, err) - } - if !nodeHeader.MetadataUUID.Equal(sb.EffectiveMetadataUUID()) { - //fmt.Printf("sector@%d does not look like a node\n", pos) - continue - } - if !nodeHeader.Checksum.Equal(CRC32c(nodeBuf[0x20:])) { - fmt.Printf("sector@%d looks like a node but is corrupt (checksum doesn't match)\n", pos) - continue - } - - fmt.Printf("node@%d: physical_addr=0x%0X logical_addr=0x%0X generation=%d owner=%v level=%d\n", - pos, pos, nodeHeader.Addr, nodeHeader.Generation, nodeHeader.Owner, nodeHeader.Level) - - pos += int64(sb.NodeSize) - int64(sb.SectorSize) - } - - return nil -} diff --git a/pkg/btrfs/io_device.go b/pkg/btrfs/io_device.go new file mode 100644 index 0000000..26450b7 --- /dev/null +++ b/pkg/btrfs/io_device.go @@ -0,0 +1,69 @@ +package btrfs + +import ( + "fmt" + "os" +) + +type Device struct { + *os.File +} + +func (dev Device) Size() (int64, error) { + fi, err := dev.Stat() + if err != nil { + return 0, err + } + return fi.Size(), nil +} + +var superblockAddrs = []int64{ + 0x00_0001_0000, // 64KiB + 0x00_0400_0000, // 64MiB + 0x40_0000_0000, // 256GiB +} + +func (dev *Device) Superblocks() ([]Ref[Superblock], error) { + const superblockSize = 0x1000 + + sz, err := dev.Size() + if err != nil { + return nil, err + } + + var ret []Ref[Superblock] + for i, addr := range superblockAddrs { + if addr+superblockSize <= sz { + superblock := Ref[Superblock]{ + dev: dev, + addr: addr, + } + if err := superblock.Read(); err != nil { + return nil, fmt.Errorf("superblock %d: %w", i, err) + } + ret = append(ret, superblock) + } + } + if len(ret) == 0 { + return nil, fmt.Errorf("no superblocks") + } + return ret, nil +} + +func (dev *Device) superblock() (ret Ref[Superblock], err error) { + sbs, err := dev.Superblocks() + if err != nil { + return ret, err + } + for i, sb := range sbs { + if err := sb.Data.ValidateChecksum(); err != nil { + return ret, fmt.Errorf("superblock %d: %w", i, err) + } + if i > 0 { + if !sb.Data.Equal(sbs[0].Data) { + return ret, fmt.Errorf("superblock %d and superblock %d disagree", 0, i) + } + } + } + return sbs[0], nil +} diff --git a/pkg/btrfs/io_fs.go b/pkg/btrfs/io_fs.go new file mode 100644 index 0000000..52f742a --- /dev/null +++ b/pkg/btrfs/io_fs.go @@ -0,0 +1,123 @@ +package btrfs + +import ( + "bytes" + "fmt" + "reflect" +) + +type FS struct { + Devices []*Device + + initErr error + uuid2dev map[UUID]*Device + chunks []SysChunk +} + +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) + } + } + return nil +} + +func (fs *FS) ReadLogicalFull(laddr LogicalAddr, dat []byte) error { + done := LogicalAddr(0) + for done < LogicalAddr(len(dat)) { + n, err := fs.readLogicalMaybeShort(laddr+done, dat[done:]) + if err != nil { + return err + } + done += LogicalAddr(n) + } + return nil +} + +func (fs *FS) readLogicalMaybeShort(laddr LogicalAddr, dat []byte) (int, error) { + if err := fs.init(); err != nil { + return 0, err + } + + 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, int64(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 +} diff --git a/pkg/btrfs/io_ref.go b/pkg/btrfs/io_ref.go new file mode 100644 index 0000000..aa37fee --- /dev/null +++ b/pkg/btrfs/io_ref.go @@ -0,0 +1,23 @@ +package btrfs + +import ( + "lukeshu.com/btrfs-tools/pkg/binstruct" +) + +type Ref[T any] struct { + dev *Device + addr int64 + Data T +} + +func (r *Ref[T]) Read() error { + size, err := binstruct.Size(r.Data) + if err != nil { + return err + } + buf := make([]byte, size) + if _, err := r.dev.ReadAt(buf, r.addr); err != nil { + return err + } + return binstruct.Unmarshal(buf, &r.Data) +} diff --git a/pkg/btrfs/bitfields.go b/pkg/btrfs/types_bitfields.go index ead4b0f..ead4b0f 100644 --- a/pkg/btrfs/bitfields.go +++ b/pkg/btrfs/types_bitfields.go diff --git a/pkg/btrfs/types_item.go b/pkg/btrfs/types_item.go new file mode 100644 index 0000000..624161e --- /dev/null +++ b/pkg/btrfs/types_item.go @@ -0,0 +1,188 @@ +package btrfs + +import ( + "fmt" +) + +type ItemType uint8 + +const ( + // inode items have the data typically returned from stat and store other + // info about object characteristics. There is one for every file and dir in + // the FS + BTRFS_INODE_ITEM_KEY = ItemType(1) + BTRFS_INODE_REF_KEY = ItemType(12) + BTRFS_INODE_EXTREF_KEY = ItemType(13) + BTRFS_XATTR_ITEM_KEY = ItemType(24) + + BTRFS_VERITY_DESC_ITEM_KEY = ItemType(36) + BTRFS_VERITY_MERKLE_ITEM_KEY = ItemType(37) + + BTRFS_ORPHAN_ITEM_KEY = ItemType(48) + + BTRFS_DIR_LOG_ITEM_KEY = ItemType(60) + BTRFS_DIR_LOG_INDEX_KEY = ItemType(72) + // dir items are the name -> inode pointers in a directory. There is one + // for every name in a directory. + BTRFS_DIR_ITEM_KEY = ItemType(84) + BTRFS_DIR_INDEX_KEY = ItemType(96) + + // extent data is for file data + BTRFS_EXTENT_DATA_KEY = ItemType(108) + + // csum items have the checksums for data in the extents + BTRFS_CSUM_ITEM_KEY = ItemType(120) + // extent csums are stored in a separate tree and hold csums for + // an entire extent on disk. + BTRFS_EXTENT_CSUM_KEY = ItemType(128) + + // root items point to tree roots. There are typically in the root + // tree used by the super block to find all the other trees + BTRFS_ROOT_ITEM_KEY = ItemType(132) + + // root backrefs tie subvols and snapshots to the directory entries that + // reference them + BTRFS_ROOT_BACKREF_KEY = ItemType(144) + + // root refs make a fast index for listing all of the snapshots and + // subvolumes referenced by a given root. They point directly to the + // directory item in the root that references the subvol + BTRFS_ROOT_REF_KEY = ItemType(156) + + // extent items are in the extent map tree. These record which blocks + // are used, and how many references there are to each block + BTRFS_EXTENT_ITEM_KEY = ItemType(168) + + // The same as the BTRFS_EXTENT_ITEM_KEY, except it's metadata we already know + // the length, so we save the level in key->offset instead of the length. + BTRFS_METADATA_ITEM_KEY = ItemType(169) + + BTRFS_TREE_BLOCK_REF_KEY = ItemType(176) + + BTRFS_EXTENT_DATA_REF_KEY = ItemType(178) + + // old style extent backrefs + BTRFS_EXTENT_REF_V0_KEY = ItemType(180) + + BTRFS_SHARED_BLOCK_REF_KEY = ItemType(182) + + BTRFS_SHARED_DATA_REF_KEY = ItemType(184) + + // block groups give us hints into the extent allocation trees. Which + // blocks are free etc etc + BTRFS_BLOCK_GROUP_ITEM_KEY = ItemType(192) + + // Every block group is represented in the free space tree by a free space info + // item, which stores some accounting information. It is keyed on + // (block_group_start, FREE_SPACE_INFO, block_group_length). + BTRFS_FREE_SPACE_INFO_KEY = ItemType(198) + + // A free space extent tracks an extent of space that is free in a block group. + // It is keyed on (start, FREE_SPACE_EXTENT, length). + BTRFS_FREE_SPACE_EXTENT_KEY = ItemType(199) + + // When a block group becomes very fragmented, we convert it to use bitmaps + // instead of extents. A free space bitmap is keyed on + // (start, FREE_SPACE_BITMAP, length); the corresponding item is a bitmap with + // (length / sectorsize) bits. + BTRFS_FREE_SPACE_BITMAP_KEY = ItemType(200) + + BTRFS_DEV_EXTENT_KEY = ItemType(204) + BTRFS_DEV_ITEM_KEY = ItemType(216) + BTRFS_CHUNK_ITEM_KEY = ItemType(228) + + // quota groups + BTRFS_QGROUP_STATUS_KEY = ItemType(240) + BTRFS_QGROUP_INFO_KEY = ItemType(242) + BTRFS_QGROUP_LIMIT_KEY = ItemType(244) + BTRFS_QGROUP_RELATION_KEY = ItemType(246) + + // The key type for tree items that are stored persistently, but do not need to + // exist for extended period of time. The items can exist in any tree. + // + // [subtype, BTRFS_TEMPORARY_ITEM_KEY, data] + // + // Existing items: + // + // - balance status item + // (BTRFS_BALANCE_OBJECTID, BTRFS_TEMPORARY_ITEM_KEY, 0) + BTRFS_TEMPORARY_ITEM_KEY = ItemType(248) + + // The key type for tree items that are stored persistently and usually exist + // for a long period, eg. filesystem lifetime. The item kinds can be status + // information, stats or preference values. The item can exist in any tree. + // + // [subtype, BTRFS_PERSISTENT_ITEM_KEY, data] + // + // Existing items: + // + // - device statistics, store IO stats in the device tree, one key for all + // stats + // (BTRFS_DEV_STATS_OBJECTID, BTRFS_DEV_STATS_KEY, 0) + BTRFS_PERSISTENT_ITEM_KEY = ItemType(249) + + // Persistently stores the device replace state in the device tree. + // The key is built like this: (0, BTRFS_DEV_REPLACE_KEY, 0). + BTRFS_DEV_REPLACE_KEY = ItemType(250) + + // Stores items that allow to quickly map UUIDs to something else. + // These items are part of the filesystem UUID tree. + // The key is built like this: + // (UUID_upper_64_bits, BTRFS_UUID_KEY*, UUID_lower_64_bits). + BTRFS_UUID_KEY_SUBVOL = ItemType(251) // for UUIDs assigned to subvols + BTRFS_UUID_KEY_RECEIVED_SUBVOL = ItemType(252) // for UUIDs assigned to received subvols + + // string items are for debugging. They just store a short string of + // data in the FS + BTRFS_STRING_ITEM_KEY = ItemType(253) +) + +func (t ItemType) String() string { + names := map[ItemType]string{ + BTRFS_INODE_ITEM_KEY: "BTRFS_INODE_ITEM_KEY", + BTRFS_INODE_REF_KEY: "BTRFS_INODE_REF_KEY", + BTRFS_INODE_EXTREF_KEY: "BTRFS_INODE_EXTREF_KEY", + BTRFS_XATTR_ITEM_KEY: "BTRFS_XATTR_ITEM_KEY", + BTRFS_VERITY_DESC_ITEM_KEY: "BTRFS_VERITY_DESC_ITEM_KEY", + BTRFS_VERITY_MERKLE_ITEM_KEY: "BTRFS_VERITY_MERKLE_ITEM_KEY", + BTRFS_ORPHAN_ITEM_KEY: "BTRFS_ORPHAN_ITEM_KEY", + BTRFS_DIR_LOG_ITEM_KEY: "BTRFS_DIR_LOG_ITEM_KEY", + BTRFS_DIR_LOG_INDEX_KEY: "BTRFS_DIR_LOG_INDEX_KEY", + BTRFS_DIR_ITEM_KEY: "BTRFS_DIR_ITEM_KEY", + BTRFS_DIR_INDEX_KEY: "BTRFS_DIR_INDEX_KEY", + BTRFS_EXTENT_DATA_KEY: "BTRFS_EXTENT_DATA_KEY", + BTRFS_CSUM_ITEM_KEY: "BTRFS_CSUM_ITEM_KEY", + BTRFS_EXTENT_CSUM_KEY: "BTRFS_EXTENT_CSUM_KEY", + BTRFS_ROOT_ITEM_KEY: "BTRFS_ROOT_ITEM_KEY", + BTRFS_ROOT_BACKREF_KEY: "BTRFS_ROOT_BACKREF_KEY", + BTRFS_ROOT_REF_KEY: "BTRFS_ROOT_REF_KEY", + BTRFS_EXTENT_ITEM_KEY: "BTRFS_EXTENT_ITEM_KEY", + BTRFS_METADATA_ITEM_KEY: "BTRFS_METADATA_ITEM_KEY", + BTRFS_TREE_BLOCK_REF_KEY: "BTRFS_TREE_BLOCK_REF_KEY", + BTRFS_EXTENT_DATA_REF_KEY: "BTRFS_EXTENT_DATA_REF_KEY", + BTRFS_EXTENT_REF_V0_KEY: "BTRFS_EXTENT_REF_V0_KEY", + BTRFS_SHARED_BLOCK_REF_KEY: "BTRFS_SHARED_BLOCK_REF_KEY", + BTRFS_SHARED_DATA_REF_KEY: "BTRFS_SHARED_DATA_REF_KEY", + BTRFS_BLOCK_GROUP_ITEM_KEY: "BTRFS_BLOCK_GROUP_ITEM_KEY", + BTRFS_FREE_SPACE_INFO_KEY: "BTRFS_FREE_SPACE_INFO_KEY", + BTRFS_FREE_SPACE_EXTENT_KEY: "BTRFS_FREE_SPACE_EXTENT_KEY", + BTRFS_FREE_SPACE_BITMAP_KEY: "BTRFS_FREE_SPACE_BITMAP_KEY", + BTRFS_DEV_EXTENT_KEY: "BTRFS_DEV_EXTENT_KEY", + BTRFS_DEV_ITEM_KEY: "BTRFS_DEV_ITEM_KEY", + BTRFS_CHUNK_ITEM_KEY: "BTRFS_CHUNK_ITEM_KEY", + BTRFS_QGROUP_STATUS_KEY: "BTRFS_QGROUP_STATUS_KEY", + BTRFS_QGROUP_INFO_KEY: "BTRFS_QGROUP_INFO_KEY", + BTRFS_QGROUP_LIMIT_KEY: "BTRFS_QGROUP_LIMIT_KEY", + BTRFS_QGROUP_RELATION_KEY: "BTRFS_QGROUP_RELATION_KEY", + BTRFS_TEMPORARY_ITEM_KEY: "BTRFS_TEMPORARY_ITEM_KEY", + BTRFS_PERSISTENT_ITEM_KEY: "BTRFS_PERSISTENT_ITEM_KEY", + BTRFS_DEV_REPLACE_KEY: "BTRFS_DEV_REPLACE_KEY", + BTRFS_UUID_KEY_SUBVOL: "BTRFS_UUID_KEY_SUBVOL", + BTRFS_UUID_KEY_RECEIVED_SUBVOL: "BTRFS_UUID_KEY_RECEIVED_SUBVOL", + BTRFS_STRING_ITEM_KEY: "BTRFS_STRING_ITEM_KEY", + } + if name, ok := names[t]; ok { + return name + } + return fmt.Sprintf("%d", t) +} diff --git a/pkg/btrfs/objid.go b/pkg/btrfs/types_objid.go index c0c73c5..9d707db 100644 --- a/pkg/btrfs/objid.go +++ b/pkg/btrfs/types_objid.go @@ -39,8 +39,9 @@ const ( BTRFS_MULTIPLE_OBJECTIDS = ObjID(maxUint64pp - 255) // dummy objectid represents multiple objectids // All files have objectids in this range. - BTRFS_FIRST_FREE_OBJECTID = ObjID(256) - BTRFS_LAST_FREE_OBJECTID = ObjID(maxUint64pp - 256) + BTRFS_FIRST_FREE_OBJECTID = ObjID(256) + BTRFS_LAST_FREE_OBJECTID = ObjID(maxUint64pp - 256) + BTRFS_FIRST_CHUNK_TREE_OBJECTID = ObjID(256) // Objects in the CHUNK_TREE diff --git a/pkg/btrfs/structs.go b/pkg/btrfs/types_structs.go index a3f7862..6ab3b93 100644 --- a/pkg/btrfs/structs.go +++ b/pkg/btrfs/types_structs.go @@ -1,6 +1,8 @@ package btrfs import ( + "fmt" + "reflect" "time" "lukeshu.com/btrfs-tools/pkg/binstruct" @@ -13,9 +15,9 @@ type ( ) type Key struct { - ObjectID ObjID `bin:"off=0, siz=8"` // Object ID. Each tree has its own set of Object IDs. - ItemType uint8 `bin:"off=8, siz=1"` // Item type. - Offset uint64 `bin:"off=9, siz=8"` // Offset. The meaning depends on the item type. + ObjectID ObjID `bin:"off=0, siz=8"` // Each tree has its own set of Object IDs. + ItemType ItemType `bin:"off=8, siz=1"` + Offset uint64 `bin:"off=9, siz=8"` // The meaning depends on the item type. binstruct.End `bin:"off=11"` } @@ -97,6 +99,29 @@ func (sb Superblock) CalculateChecksum() (CSum, error) { return CRC32c(data[0x20:]), nil } +func (sb Superblock) ValidateChecksum() error { + stored := sb.Checksum + calced, err := sb.CalculateChecksum() + if err != nil { + return err + } + if !calced.Equal(stored) { + return fmt.Errorf("superblock checksum mismatch: stored=%s calculated=%s", + stored, calced) + } + return nil +} + +func (a Superblock) Equal(b Superblock) bool { + a.Checksum = CSum{} + a.Self = 0 + + b.Checksum = CSum{} + b.Self = 0 + + return reflect.DeepEqual(a, b) +} + func (sb Superblock) EffectiveMetadataUUID() UUID { if !sb.IncompatFlags.Has(FeatureIncompatMetadataUUID) { return sb.FSUUID @@ -106,7 +131,7 @@ func (sb Superblock) EffectiveMetadataUUID() UUID { type SysChunk struct { Key `bin:"off=0, siz=11"` - ChunkItem `bin:"off=11, siz=30"` + Chunk `bin:"off=11, siz=30"` binstruct.End `bin:"off=41"` } @@ -120,12 +145,12 @@ func (sb Superblock) ParseSysChunkArray() ([]SysChunk, error) { } dat = dat[0x41:] - for i := 0; i < int(pair.ChunkItem.NumStripes); i++ { - var stripe ChunkItemStripe + for i := 0; i < int(pair.Chunk.NumStripes); i++ { + var stripe Stripe if err := binstruct.Unmarshal(dat, &stripe); err != nil { return nil, err } - pair.ChunkItem.Stripes = append(pair.ChunkItem.Stripes, stripe) + pair.Chunk.Stripes = append(pair.Chunk.Stripes, stripe) dat = dat[0x20:] } @@ -230,10 +255,10 @@ type DevItem struct { binstruct.End `bin:"off=62"` } -type ChunkItem struct { +type Chunk struct { // Maps logical address to physical. Size uint64 `bin:"off=0, siz=8"` // size of chunk (bytes) - Root ObjID `bin:"off=8, siz=8"` // root referencing this chunk (2) + Owner ObjID `bin:"off=8, siz=8"` // root referencing this chunk (2) StripeLen uint64 `bin:"off=10, siz=8"` // stripe length Type uint64 `bin:"off=18, siz=8"` // type (same as flags for block group?) IOOptimalAlign uint32 `bin:"off=20, siz=4"` // optimal io alignment @@ -242,10 +267,10 @@ type ChunkItem struct { NumStripes uint16 `bin:"off=2c, siz=2"` // number of stripes SubStripes uint16 `bin:"off=2e, siz=2"` // sub stripes binstruct.End `bin:"off=30"` - Stripes []ChunkItemStripe `bin:"-"` + Stripes []Stripe `bin:"-"` } -type ChunkItemStripe struct { +type Stripe struct { // Stripes follow (for each number of stripes): DeviceID ObjID `bin:"off=0, siz=8"` // device ID Offset uint64 `bin:"off=8, siz=8"` // offset diff --git a/pkg/btrfs/uuid.go b/pkg/btrfs/types_uuid.go index b9e3e0c..b9e3e0c 100644 --- a/pkg/btrfs/uuid.go +++ b/pkg/btrfs/types_uuid.go |