summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/btrfs-dbg/main.go16
-rw-r--r--cmd/btrfs-dump-tree/main.go67
-rw-r--r--notes.org4
-rw-r--r--pkg/btrfs/fsck.go52
-rw-r--r--pkg/btrfs/image.go116
-rw-r--r--pkg/btrfs/io_device.go69
-rw-r--r--pkg/btrfs/io_fs.go123
-rw-r--r--pkg/btrfs/io_ref.go23
-rw-r--r--pkg/btrfs/types_bitfields.go (renamed from pkg/btrfs/bitfields.go)0
-rw-r--r--pkg/btrfs/types_item.go188
-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