From 23ab1f8be6a1f4b5ce01e05f8ed3f6b5dae30d0b Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Tue, 24 May 2022 21:53:28 -0600 Subject: stuff --- pkg/btrfs/bitfields.go | 68 ----------- pkg/btrfs/fsck.go | 52 ++++++++ pkg/btrfs/image.go | 116 ------------------ pkg/btrfs/io_device.go | 69 +++++++++++ pkg/btrfs/io_fs.go | 123 +++++++++++++++++++ pkg/btrfs/io_ref.go | 23 ++++ pkg/btrfs/objid.go | 95 --------------- pkg/btrfs/structs.go | 254 --------------------------------------- pkg/btrfs/types_bitfields.go | 68 +++++++++++ pkg/btrfs/types_item.go | 188 +++++++++++++++++++++++++++++ pkg/btrfs/types_objid.go | 96 +++++++++++++++ pkg/btrfs/types_structs.go | 279 +++++++++++++++++++++++++++++++++++++++++++ pkg/btrfs/types_uuid.go | 24 ++++ pkg/btrfs/uuid.go | 24 ---- 14 files changed, 922 insertions(+), 557 deletions(-) delete mode 100644 pkg/btrfs/bitfields.go create mode 100644 pkg/btrfs/fsck.go delete mode 100644 pkg/btrfs/image.go create mode 100644 pkg/btrfs/io_device.go create mode 100644 pkg/btrfs/io_fs.go create mode 100644 pkg/btrfs/io_ref.go delete mode 100644 pkg/btrfs/objid.go delete mode 100644 pkg/btrfs/structs.go create mode 100644 pkg/btrfs/types_bitfields.go create mode 100644 pkg/btrfs/types_item.go create mode 100644 pkg/btrfs/types_objid.go create mode 100644 pkg/btrfs/types_structs.go create mode 100644 pkg/btrfs/types_uuid.go delete mode 100644 pkg/btrfs/uuid.go (limited to 'pkg/btrfs') diff --git a/pkg/btrfs/bitfields.go b/pkg/btrfs/bitfields.go deleted file mode 100644 index ead4b0f..0000000 --- a/pkg/btrfs/bitfields.go +++ /dev/null @@ -1,68 +0,0 @@ -package btrfs - -import ( - "fmt" - "strings" -) - -func bitfieldString[T ~uint8 | ~uint16 | ~uint32 | ~uint64](bitfield T, bitnames []string) string { - if bitfield == 0 { - return "0" - } - var out strings.Builder - fmt.Fprintf(&out, "(0x%0X)", uint64(bitfield)) - rest := bitfield - sep := ' ' - for i := 0; rest != 0; i++ { - if rest&(1< 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/objid.go b/pkg/btrfs/objid.go deleted file mode 100644 index c0c73c5..0000000 --- a/pkg/btrfs/objid.go +++ /dev/null @@ -1,95 +0,0 @@ -package btrfs - -import ( - "fmt" -) - -type ObjID uint64 - -const maxUint64pp = 0x1_0000_0000 - -const ( - // The IDs of the various trees - BTRFS_ROOT_TREE_OBJECTID = ObjID(1) // holds pointers to all of the tree roots - BTRFS_EXTENT_TREE_OBJECTID = ObjID(2) // stores information about which extents are in use, and reference counts - BTRFS_CHUNK_TREE_OBJECTID = ObjID(3) // chunk tree stores translations from logical -> physical block numbering - BTRFS_DEV_TREE_OBJECTID = ObjID(4) // stores info about which areas of a given device are in use; one per device - BTRFS_FS_TREE_OBJECTID = ObjID(5) // one per subvolume, storing files and directories - BTRFS_ROOT_TREE_DIR_OBJECTID = ObjID(6) // directory objectid inside the root tree - BTRFS_CSUM_TREE_OBJECTID = ObjID(7) // holds checksums of all the data extents - BTRFS_QUOTA_TREE_OBJECTID = ObjID(8) - BTRFS_UUID_TREE_OBJECTID = ObjID(9) // for storing items that use the BTRFS_UUID_KEY* - BTRFS_FREE_SPACE_TREE_OBJECTID = ObjID(10) // tracks free space in block groups. - BTRFS_BLOCK_GROUP_TREE_OBJECTID = ObjID(11) // hold the block group items. - - // Objects in the DEV_TREE - BTRFS_DEV_STATS_OBJECTID = ObjID(0) // device stats in the device tree - - // ??? - BTRFS_BALANCE_OBJECTID = ObjID(maxUint64pp - 4) // for storing balance parameters in the root tree - BTRFS_ORPHAN_OBJECTID = ObjID(maxUint64pp - 5) // orphan objectid for tracking unlinked/truncated files - BTRFS_TREE_LOG_OBJECTID = ObjID(maxUint64pp - 6) // does write ahead logging to speed up fsyncs - BTRFS_TREE_LOG_FIXUP_OBJECTID = ObjID(maxUint64pp - 7) - BTRFS_TREE_RELOC_OBJECTID = ObjID(maxUint64pp - 8) // space balancing - BTRFS_DATA_RELOC_TREE_OBJECTID = ObjID(maxUint64pp - 9) - BTRFS_EXTENT_CSUM_OBJECTID = ObjID(maxUint64pp - 10) // extent checksums all have this objectid - BTRFS_FREE_SPACE_OBJECTID = ObjID(maxUint64pp - 11) // For storing free space cache - BTRFS_FREE_INO_OBJECTID = ObjID(maxUint64pp - 12) // stores the inode number for the free-ino cache - - 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_CHUNK_TREE_OBJECTID = ObjID(256) - - // Objects in the CHUNK_TREE - BTRFS_DEV_ITEMS_OBJECTID = ObjID(1) - - // ??? - BTRFS_EMPTY_SUBVOL_DIR_OBJECTID = ObjID(2) -) - -func (id ObjID) String() string { - if id > BTRFS_LAST_FREE_OBJECTID { - names := map[ObjID]string{ - BTRFS_BALANCE_OBJECTID: "BTRFS_BALANCE_OBJECTID", - BTRFS_ORPHAN_OBJECTID: "BTRFS_ORPHAN_OBJECTID", - BTRFS_TREE_LOG_OBJECTID: "BTRFS_TREE_LOG_OBJECTID", - BTRFS_TREE_LOG_FIXUP_OBJECTID: "BTRFS_TREE_LOG_FIXUP_OBJECTID", - BTRFS_TREE_RELOC_OBJECTID: "BTRFS_TREE_RELOC_OBJECTID", - BTRFS_DATA_RELOC_TREE_OBJECTID: "BTRFS_DATA_RELOC_TREE_OBJECTID", - BTRFS_EXTENT_CSUM_OBJECTID: "BTRFS_EXTENT_CSUM_OBJECTID", - BTRFS_FREE_SPACE_OBJECTID: "BTRFS_FREE_SPACE_OBJECTID", - BTRFS_FREE_INO_OBJECTID: "BTRFS_FREE_INO_OBJECTID", - BTRFS_MULTIPLE_OBJECTIDS: "BTRFS_MULTIPLE_OBJECTIDS", - } - if name, ok := names[id]; ok { - return name - } - return fmt.Sprintf("%d", int64(id)) - } - return fmt.Sprintf("%d", id) -} - -type TreeObjID ObjID - -func (id TreeObjID) String() string { - names := map[ObjID]string{ - BTRFS_ROOT_TREE_OBJECTID: "BTRFS_ROOT_TREE_OBJECTID", - BTRFS_EXTENT_TREE_OBJECTID: "BTRFS_EXTENT_TREE_OBJECTID", - BTRFS_CHUNK_TREE_OBJECTID: "BTRFS_CHUNK_TREE_OBJECTID", - BTRFS_DEV_TREE_OBJECTID: "BTRFS_DEV_TREE_OBJECTID", - BTRFS_FS_TREE_OBJECTID: "BTRFS_FS_TREE_OBJECTID", - BTRFS_ROOT_TREE_DIR_OBJECTID: "BTRFS_ROOT_TREE_DIR_OBJECTID", - BTRFS_CSUM_TREE_OBJECTID: "BTRFS_CSUM_TREE_OBJECTID", - BTRFS_QUOTA_TREE_OBJECTID: "BTRFS_QUOTA_TREE_OBJECTID", - BTRFS_UUID_TREE_OBJECTID: "BTRFS_UUID_TREE_OBJECTID", - BTRFS_FREE_SPACE_TREE_OBJECTID: "BTRFS_FREE_SPACE_TREE_OBJECTID", - BTRFS_BLOCK_GROUP_TREE_OBJECTID: "BTRFS_BLOCK_GROUP_TREE_OBJECTID", - } - if name, ok := names[ObjID(id)]; ok { - return name - } - return ObjID(id).String() -} diff --git a/pkg/btrfs/structs.go b/pkg/btrfs/structs.go deleted file mode 100644 index a3f7862..0000000 --- a/pkg/btrfs/structs.go +++ /dev/null @@ -1,254 +0,0 @@ -package btrfs - -import ( - "time" - - "lukeshu.com/btrfs-tools/pkg/binstruct" -) - -type ( - PhysicalAddr int64 - LogicalAddr int64 - Generation uint64 -) - -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. - binstruct.End `bin:"off=11"` -} - -type Time struct { - Sec int64 `bin:"off=0, siz=8"` // Number of seconds since 1970-01-01T00:00:00Z. - NSec uint64 `bin:"off=8, siz=4"` // Number of nanoseconds since the beginning of the second. - binstruct.End `bin:"off=c"` -} - -func (t Time) ToStd() time.Time { - return time.Unix(t.Sec, int64(t.NSec)) -} - -type Superblock struct { - Checksum CSum `bin:"off=0, siz=20"` // Checksum of everything past this field (from 20 to 1000) - FSUUID UUID `bin:"off=20, siz=10"` // FS UUID - Self PhysicalAddr `bin:"off=30, siz=8"` // physical address of this block (different for mirrors) - Flags uint64 `bin:"off=38, siz=8"` // flags - Magic [8]byte `bin:"off=40, siz=8"` // magic ('_BHRfS_M') - Generation Generation `bin:"off=48, siz=8"` - - RootTree LogicalAddr `bin:"off=50, siz=8"` // logical address of the root tree root - ChunkTree LogicalAddr `bin:"off=58, siz=8"` // logical address of the chunk tree root - LogTree LogicalAddr `bin:"off=60, siz=8"` // logical address of the log tree root - - LogRootTransID uint64 `bin:"off=68, siz=8"` // log_root_transid - TotalBytes uint64 `bin:"off=70, siz=8"` // total_bytes - BytesUsed uint64 `bin:"off=78, siz=8"` // bytes_used - RootDirObjectID ObjID `bin:"off=80, siz=8"` // root_dir_objectid (usually 6) - NumDevices uint64 `bin:"off=88, siz=8"` // num_devices - - SectorSize uint32 `bin:"off=90, siz=4"` - NodeSize uint32 `bin:"off=94, siz=4"` - LeafSize uint32 `bin:"off=98, siz=4"` // unused; must be the same as NodeSize - StripeSize uint32 `bin:"off=9c, siz=4"` - SysChunkArraySize uint32 `bin:"off=a0, siz=4"` - - ChunkRootGeneration Generation `bin:"off=a4, siz=8"` - CompatFlags uint64 `bin:"off=ac, siz=8"` // compat_flags - CompatROFlags uint64 `bin:"off=b4, siz=8"` // compat_ro_flags - only implementations that support the flags can write to the filesystem - IncompatFlags IncompatFlags `bin:"off=bc, siz=8"` // incompat_flags - only implementations that support the flags can use the filesystem - ChecksumType uint16 `bin:"off=c4, siz=2"` // csum_type - Btrfs currently uses the CRC32c little-endian hash function with seed -1. - - RootLevel uint8 `bin:"off=c6, siz=1"` // root_level - ChunkLevel uint8 `bin:"off=c7, siz=1"` // chunk_root_level - LogLevel uint8 `bin:"off=c8, siz=1"` // log_root_level - - DevItem DevItem `bin:"off=c9, siz=62"` // DEV_ITEM data for this device - Label [0x100]byte `bin:"off=12b, siz=100"` // label (may not contain '/' or '\\') - CacheGeneration Generation `bin:"off=22b, siz=8"` - UUIDTreeGeneration uint64 `bin:"off=233, siz=8"` // uuid_tree_generation - - // FeatureIncompatMetadataUUID - MetadataUUID UUID `bin:"off=23b, siz=10"` - - // FeatureIncompatExtentTreeV2 - NumGlobalRoots uint64 `bin:"off=24b, siz=8"` - - // FeatureIncompatExtentTreeV2 - BlockGroupRoot ObjID `bin:"off=253, siz=8"` - BlockGroupRootGeneration Generation `bin:"off=25b, siz=8"` - BlockGroupRootLevel uint8 `bin:"off=263, siz=1"` - - Reserved [199]byte `bin:"off=264, siz=c7"` // future expansion - - SysChunkArray [0x800]byte `bin:"off=32b, siz=800"` // sys_chunk_array:(n bytes valid) Contains (KEY . CHUNK_ITEM) pairs for all SYSTEM chunks. This is needed to bootstrap the mapping from logical addresses to physical. - SuperRoots [4]RootBackup `bin:"off=b2b, siz=2a0"` - - // Padded to 4096 bytes - Padding [565]byte `bin:"off=dcb, siz=235"` - binstruct.End `bin:"off=1000"` -} - -func (sb Superblock) CalculateChecksum() (CSum, error) { - data, err := binstruct.Marshal(sb) - if err != nil { - return CSum{}, err - } - return CRC32c(data[0x20:]), nil -} - -func (sb Superblock) EffectiveMetadataUUID() UUID { - if !sb.IncompatFlags.Has(FeatureIncompatMetadataUUID) { - return sb.FSUUID - } - return sb.MetadataUUID -} - -type SysChunk struct { - Key `bin:"off=0, siz=11"` - ChunkItem `bin:"off=11, siz=30"` - binstruct.End `bin:"off=41"` -} - -func (sb Superblock) ParseSysChunkArray() ([]SysChunk, error) { - dat := sb.SysChunkArray[:sb.SysChunkArraySize] - var ret []SysChunk - for len(dat) > 0 { - var pair SysChunk - if err := binstruct.Unmarshal(dat, &pair); err != nil { - return nil, err - } - dat = dat[0x41:] - - for i := 0; i < int(pair.ChunkItem.NumStripes); i++ { - var stripe ChunkItemStripe - if err := binstruct.Unmarshal(dat, &stripe); err != nil { - return nil, err - } - pair.ChunkItem.Stripes = append(pair.ChunkItem.Stripes, stripe) - dat = dat[0x20:] - } - - ret = append(ret, pair) - } - return ret, nil -} - -type RootBackup struct { - TreeRoot ObjID `bin:"off=0, siz=8"` - TreeRootGen Generation `bin:"off=8, siz=8"` - - ChunkRoot ObjID `bin:"off=10, siz=8"` - ChunkRootGen Generation `bin:"off=18, siz=8"` - - ExtentRoot ObjID `bin:"off=20, siz=8"` - ExtentRootGen Generation `bin:"off=28, siz=8"` - - FSRoot ObjID `bin:"off=30, siz=8"` - FSRootGen Generation `bin:"off=38, siz=8"` - - DevRoot ObjID `bin:"off=40, siz=8"` - DevRootGen Generation `bin:"off=48, siz=8"` - - ChecksumRoot ObjID `bin:"off=50, siz=8"` - ChecksumRootGen Generation `bin:"off=58, siz=8"` - - TotalBytes uint64 `bin:"off=60, siz=8"` - BytesUsed uint64 `bin:"off=68, siz=8"` - NumDevices uint64 `bin:"off=70, siz=8"` - - Unused [8 * 4]byte `bin:"off=78, siz=20"` - - TreeRootLevel uint8 `bin:"off=98, siz=1"` - ChunkRootLevel uint8 `bin:"off=99, siz=1"` - ExtentRootLevel uint8 `bin:"off=9a, siz=1"` - FSRootLevel uint8 `bin:"off=9b, siz=1"` - DevRootLevel uint8 `bin:"off=9c, siz=1"` - ChecksumRootLevel uint8 `bin:"off=9d, siz=1"` - - Padding [10]byte `bin:"off=9e, siz=a"` - binstruct.End `bin:"off=a8"` -} - -type NodeHeader struct { - Checksum CSum `bin:"off=0, siz=20"` // Checksum of everything after this field (from 20 to the end of the node) - MetadataUUID UUID `bin:"off=20, siz=10"` // FS UUID - Addr LogicalAddr `bin:"off=30, siz=8"` // Logical address of this node - Flags uint64 `bin:"off=38, siz=8"` // Flags - ChunkTreeUUID UUID `bin:"off=40, siz=10"` // Chunk tree UUID - Generation Generation `bin:"off=50, siz=8"` // Generation - Owner TreeObjID `bin:"off=58, siz=8"` // The ID of the tree that contains this node - NumItems uint32 `bin:"off=60, siz=4"` // Number of items - Level uint8 `bin:"off=64, siz=1"` // Level (0 for leaf nodes) - binstruct.End `bin:"off=65"` -} - -type InternalNode struct { - NodeHeader - Body []KeyPointer -} - -type KeyPointer struct { - Key Key `bin:"off=0, siz=11"` - BlockNumber uint64 `bin:"off=11, siz=8"` - Generation Generation `bin:"off=19, siz=8"` - binstruct.End `bin:"off=21"` -} - -type LeafNode struct { - NodeHeader - Body []Item -} - -type Item struct { - Key Key `bin:"off=0, siz=11"` - DataOffset uint32 `bin:"off=11, siz=4"` // relative to the end of the header (0x65) - DataSize uint32 `bin:"off=15, siz=4"` - binstruct.End `bin:"off=19"` -} - -type DevItem struct { - DeviceID ObjID `bin:"off=0, siz=8"` // device ID - - NumBytes uint64 `bin:"off=8, siz=8"` // number of bytes - NumBytesUsed uint64 `bin:"off=10, siz=8"` // number of bytes used - - IOOptimalAlign uint32 `bin:"off=18, siz=4"` // optimal I/O align - IOOptimalWidth uint32 `bin:"off=1c, siz=4"` // optimal I/O width - IOMinSize uint32 `bin:"off=20, siz=4"` // minimal I/O size (sector size) - - Type uint64 `bin:"off=24, siz=8"` // type - Generation Generation `bin:"off=2c, siz=8"` // generation - StartOffset uint64 `bin:"off=34, siz=8"` // start offset - DevGroup uint32 `bin:"off=3c, siz=4"` // dev group - SeekSpeed uint8 `bin:"off=40, siz=1"` // seek speed - Bandwidth uint8 `bin:"off=41, siz=1"` // bandwidth - - DevUUID UUID `bin:"off=42, siz=10"` // device UUID - FSUUID UUID `bin:"off=52, siz=10"` // FS UUID - - binstruct.End `bin:"off=62"` -} - -type ChunkItem 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) - 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 - IOOptimalWidth uint32 `bin:"off=24, siz=4"` // optimal io width - IoMinSize uint32 `bin:"off=28, siz=4"` // minimal io size (sector size) - 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:"-"` -} - -type ChunkItemStripe 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 - DeviceUUID UUID `bin:"off=10, siz=10"` // device UUID - binstruct.End `bin:"off=20"` -} diff --git a/pkg/btrfs/types_bitfields.go b/pkg/btrfs/types_bitfields.go new file mode 100644 index 0000000..ead4b0f --- /dev/null +++ b/pkg/btrfs/types_bitfields.go @@ -0,0 +1,68 @@ +package btrfs + +import ( + "fmt" + "strings" +) + +func bitfieldString[T ~uint8 | ~uint16 | ~uint32 | ~uint64](bitfield T, bitnames []string) string { + if bitfield == 0 { + return "0" + } + var out strings.Builder + fmt.Fprintf(&out, "(0x%0X)", uint64(bitfield)) + rest := bitfield + sep := ' ' + for i := 0; rest != 0; i++ { + if rest&(1< 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/types_objid.go b/pkg/btrfs/types_objid.go new file mode 100644 index 0000000..9d707db --- /dev/null +++ b/pkg/btrfs/types_objid.go @@ -0,0 +1,96 @@ +package btrfs + +import ( + "fmt" +) + +type ObjID uint64 + +const maxUint64pp = 0x1_0000_0000 + +const ( + // The IDs of the various trees + BTRFS_ROOT_TREE_OBJECTID = ObjID(1) // holds pointers to all of the tree roots + BTRFS_EXTENT_TREE_OBJECTID = ObjID(2) // stores information about which extents are in use, and reference counts + BTRFS_CHUNK_TREE_OBJECTID = ObjID(3) // chunk tree stores translations from logical -> physical block numbering + BTRFS_DEV_TREE_OBJECTID = ObjID(4) // stores info about which areas of a given device are in use; one per device + BTRFS_FS_TREE_OBJECTID = ObjID(5) // one per subvolume, storing files and directories + BTRFS_ROOT_TREE_DIR_OBJECTID = ObjID(6) // directory objectid inside the root tree + BTRFS_CSUM_TREE_OBJECTID = ObjID(7) // holds checksums of all the data extents + BTRFS_QUOTA_TREE_OBJECTID = ObjID(8) + BTRFS_UUID_TREE_OBJECTID = ObjID(9) // for storing items that use the BTRFS_UUID_KEY* + BTRFS_FREE_SPACE_TREE_OBJECTID = ObjID(10) // tracks free space in block groups. + BTRFS_BLOCK_GROUP_TREE_OBJECTID = ObjID(11) // hold the block group items. + + // Objects in the DEV_TREE + BTRFS_DEV_STATS_OBJECTID = ObjID(0) // device stats in the device tree + + // ??? + BTRFS_BALANCE_OBJECTID = ObjID(maxUint64pp - 4) // for storing balance parameters in the root tree + BTRFS_ORPHAN_OBJECTID = ObjID(maxUint64pp - 5) // orphan objectid for tracking unlinked/truncated files + BTRFS_TREE_LOG_OBJECTID = ObjID(maxUint64pp - 6) // does write ahead logging to speed up fsyncs + BTRFS_TREE_LOG_FIXUP_OBJECTID = ObjID(maxUint64pp - 7) + BTRFS_TREE_RELOC_OBJECTID = ObjID(maxUint64pp - 8) // space balancing + BTRFS_DATA_RELOC_TREE_OBJECTID = ObjID(maxUint64pp - 9) + BTRFS_EXTENT_CSUM_OBJECTID = ObjID(maxUint64pp - 10) // extent checksums all have this objectid + BTRFS_FREE_SPACE_OBJECTID = ObjID(maxUint64pp - 11) // For storing free space cache + BTRFS_FREE_INO_OBJECTID = ObjID(maxUint64pp - 12) // stores the inode number for the free-ino cache + + 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_CHUNK_TREE_OBJECTID = ObjID(256) + + // Objects in the CHUNK_TREE + BTRFS_DEV_ITEMS_OBJECTID = ObjID(1) + + // ??? + BTRFS_EMPTY_SUBVOL_DIR_OBJECTID = ObjID(2) +) + +func (id ObjID) String() string { + if id > BTRFS_LAST_FREE_OBJECTID { + names := map[ObjID]string{ + BTRFS_BALANCE_OBJECTID: "BTRFS_BALANCE_OBJECTID", + BTRFS_ORPHAN_OBJECTID: "BTRFS_ORPHAN_OBJECTID", + BTRFS_TREE_LOG_OBJECTID: "BTRFS_TREE_LOG_OBJECTID", + BTRFS_TREE_LOG_FIXUP_OBJECTID: "BTRFS_TREE_LOG_FIXUP_OBJECTID", + BTRFS_TREE_RELOC_OBJECTID: "BTRFS_TREE_RELOC_OBJECTID", + BTRFS_DATA_RELOC_TREE_OBJECTID: "BTRFS_DATA_RELOC_TREE_OBJECTID", + BTRFS_EXTENT_CSUM_OBJECTID: "BTRFS_EXTENT_CSUM_OBJECTID", + BTRFS_FREE_SPACE_OBJECTID: "BTRFS_FREE_SPACE_OBJECTID", + BTRFS_FREE_INO_OBJECTID: "BTRFS_FREE_INO_OBJECTID", + BTRFS_MULTIPLE_OBJECTIDS: "BTRFS_MULTIPLE_OBJECTIDS", + } + if name, ok := names[id]; ok { + return name + } + return fmt.Sprintf("%d", int64(id)) + } + return fmt.Sprintf("%d", id) +} + +type TreeObjID ObjID + +func (id TreeObjID) String() string { + names := map[ObjID]string{ + BTRFS_ROOT_TREE_OBJECTID: "BTRFS_ROOT_TREE_OBJECTID", + BTRFS_EXTENT_TREE_OBJECTID: "BTRFS_EXTENT_TREE_OBJECTID", + BTRFS_CHUNK_TREE_OBJECTID: "BTRFS_CHUNK_TREE_OBJECTID", + BTRFS_DEV_TREE_OBJECTID: "BTRFS_DEV_TREE_OBJECTID", + BTRFS_FS_TREE_OBJECTID: "BTRFS_FS_TREE_OBJECTID", + BTRFS_ROOT_TREE_DIR_OBJECTID: "BTRFS_ROOT_TREE_DIR_OBJECTID", + BTRFS_CSUM_TREE_OBJECTID: "BTRFS_CSUM_TREE_OBJECTID", + BTRFS_QUOTA_TREE_OBJECTID: "BTRFS_QUOTA_TREE_OBJECTID", + BTRFS_UUID_TREE_OBJECTID: "BTRFS_UUID_TREE_OBJECTID", + BTRFS_FREE_SPACE_TREE_OBJECTID: "BTRFS_FREE_SPACE_TREE_OBJECTID", + BTRFS_BLOCK_GROUP_TREE_OBJECTID: "BTRFS_BLOCK_GROUP_TREE_OBJECTID", + } + if name, ok := names[ObjID(id)]; ok { + return name + } + return ObjID(id).String() +} diff --git a/pkg/btrfs/types_structs.go b/pkg/btrfs/types_structs.go new file mode 100644 index 0000000..6ab3b93 --- /dev/null +++ b/pkg/btrfs/types_structs.go @@ -0,0 +1,279 @@ +package btrfs + +import ( + "fmt" + "reflect" + "time" + + "lukeshu.com/btrfs-tools/pkg/binstruct" +) + +type ( + PhysicalAddr int64 + LogicalAddr int64 + Generation uint64 +) + +type Key struct { + 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"` +} + +type Time struct { + Sec int64 `bin:"off=0, siz=8"` // Number of seconds since 1970-01-01T00:00:00Z. + NSec uint64 `bin:"off=8, siz=4"` // Number of nanoseconds since the beginning of the second. + binstruct.End `bin:"off=c"` +} + +func (t Time) ToStd() time.Time { + return time.Unix(t.Sec, int64(t.NSec)) +} + +type Superblock struct { + Checksum CSum `bin:"off=0, siz=20"` // Checksum of everything past this field (from 20 to 1000) + FSUUID UUID `bin:"off=20, siz=10"` // FS UUID + Self PhysicalAddr `bin:"off=30, siz=8"` // physical address of this block (different for mirrors) + Flags uint64 `bin:"off=38, siz=8"` // flags + Magic [8]byte `bin:"off=40, siz=8"` // magic ('_BHRfS_M') + Generation Generation `bin:"off=48, siz=8"` + + RootTree LogicalAddr `bin:"off=50, siz=8"` // logical address of the root tree root + ChunkTree LogicalAddr `bin:"off=58, siz=8"` // logical address of the chunk tree root + LogTree LogicalAddr `bin:"off=60, siz=8"` // logical address of the log tree root + + LogRootTransID uint64 `bin:"off=68, siz=8"` // log_root_transid + TotalBytes uint64 `bin:"off=70, siz=8"` // total_bytes + BytesUsed uint64 `bin:"off=78, siz=8"` // bytes_used + RootDirObjectID ObjID `bin:"off=80, siz=8"` // root_dir_objectid (usually 6) + NumDevices uint64 `bin:"off=88, siz=8"` // num_devices + + SectorSize uint32 `bin:"off=90, siz=4"` + NodeSize uint32 `bin:"off=94, siz=4"` + LeafSize uint32 `bin:"off=98, siz=4"` // unused; must be the same as NodeSize + StripeSize uint32 `bin:"off=9c, siz=4"` + SysChunkArraySize uint32 `bin:"off=a0, siz=4"` + + ChunkRootGeneration Generation `bin:"off=a4, siz=8"` + CompatFlags uint64 `bin:"off=ac, siz=8"` // compat_flags + CompatROFlags uint64 `bin:"off=b4, siz=8"` // compat_ro_flags - only implementations that support the flags can write to the filesystem + IncompatFlags IncompatFlags `bin:"off=bc, siz=8"` // incompat_flags - only implementations that support the flags can use the filesystem + ChecksumType uint16 `bin:"off=c4, siz=2"` // csum_type - Btrfs currently uses the CRC32c little-endian hash function with seed -1. + + RootLevel uint8 `bin:"off=c6, siz=1"` // root_level + ChunkLevel uint8 `bin:"off=c7, siz=1"` // chunk_root_level + LogLevel uint8 `bin:"off=c8, siz=1"` // log_root_level + + DevItem DevItem `bin:"off=c9, siz=62"` // DEV_ITEM data for this device + Label [0x100]byte `bin:"off=12b, siz=100"` // label (may not contain '/' or '\\') + CacheGeneration Generation `bin:"off=22b, siz=8"` + UUIDTreeGeneration uint64 `bin:"off=233, siz=8"` // uuid_tree_generation + + // FeatureIncompatMetadataUUID + MetadataUUID UUID `bin:"off=23b, siz=10"` + + // FeatureIncompatExtentTreeV2 + NumGlobalRoots uint64 `bin:"off=24b, siz=8"` + + // FeatureIncompatExtentTreeV2 + BlockGroupRoot ObjID `bin:"off=253, siz=8"` + BlockGroupRootGeneration Generation `bin:"off=25b, siz=8"` + BlockGroupRootLevel uint8 `bin:"off=263, siz=1"` + + Reserved [199]byte `bin:"off=264, siz=c7"` // future expansion + + SysChunkArray [0x800]byte `bin:"off=32b, siz=800"` // sys_chunk_array:(n bytes valid) Contains (KEY . CHUNK_ITEM) pairs for all SYSTEM chunks. This is needed to bootstrap the mapping from logical addresses to physical. + SuperRoots [4]RootBackup `bin:"off=b2b, siz=2a0"` + + // Padded to 4096 bytes + Padding [565]byte `bin:"off=dcb, siz=235"` + binstruct.End `bin:"off=1000"` +} + +func (sb Superblock) CalculateChecksum() (CSum, error) { + data, err := binstruct.Marshal(sb) + if err != nil { + return CSum{}, err + } + 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 + } + return sb.MetadataUUID +} + +type SysChunk struct { + Key `bin:"off=0, siz=11"` + Chunk `bin:"off=11, siz=30"` + binstruct.End `bin:"off=41"` +} + +func (sb Superblock) ParseSysChunkArray() ([]SysChunk, error) { + dat := sb.SysChunkArray[:sb.SysChunkArraySize] + var ret []SysChunk + for len(dat) > 0 { + var pair SysChunk + if err := binstruct.Unmarshal(dat, &pair); err != nil { + return nil, err + } + dat = dat[0x41:] + + for i := 0; i < int(pair.Chunk.NumStripes); i++ { + var stripe Stripe + if err := binstruct.Unmarshal(dat, &stripe); err != nil { + return nil, err + } + pair.Chunk.Stripes = append(pair.Chunk.Stripes, stripe) + dat = dat[0x20:] + } + + ret = append(ret, pair) + } + return ret, nil +} + +type RootBackup struct { + TreeRoot ObjID `bin:"off=0, siz=8"` + TreeRootGen Generation `bin:"off=8, siz=8"` + + ChunkRoot ObjID `bin:"off=10, siz=8"` + ChunkRootGen Generation `bin:"off=18, siz=8"` + + ExtentRoot ObjID `bin:"off=20, siz=8"` + ExtentRootGen Generation `bin:"off=28, siz=8"` + + FSRoot ObjID `bin:"off=30, siz=8"` + FSRootGen Generation `bin:"off=38, siz=8"` + + DevRoot ObjID `bin:"off=40, siz=8"` + DevRootGen Generation `bin:"off=48, siz=8"` + + ChecksumRoot ObjID `bin:"off=50, siz=8"` + ChecksumRootGen Generation `bin:"off=58, siz=8"` + + TotalBytes uint64 `bin:"off=60, siz=8"` + BytesUsed uint64 `bin:"off=68, siz=8"` + NumDevices uint64 `bin:"off=70, siz=8"` + + Unused [8 * 4]byte `bin:"off=78, siz=20"` + + TreeRootLevel uint8 `bin:"off=98, siz=1"` + ChunkRootLevel uint8 `bin:"off=99, siz=1"` + ExtentRootLevel uint8 `bin:"off=9a, siz=1"` + FSRootLevel uint8 `bin:"off=9b, siz=1"` + DevRootLevel uint8 `bin:"off=9c, siz=1"` + ChecksumRootLevel uint8 `bin:"off=9d, siz=1"` + + Padding [10]byte `bin:"off=9e, siz=a"` + binstruct.End `bin:"off=a8"` +} + +type NodeHeader struct { + Checksum CSum `bin:"off=0, siz=20"` // Checksum of everything after this field (from 20 to the end of the node) + MetadataUUID UUID `bin:"off=20, siz=10"` // FS UUID + Addr LogicalAddr `bin:"off=30, siz=8"` // Logical address of this node + Flags uint64 `bin:"off=38, siz=8"` // Flags + ChunkTreeUUID UUID `bin:"off=40, siz=10"` // Chunk tree UUID + Generation Generation `bin:"off=50, siz=8"` // Generation + Owner TreeObjID `bin:"off=58, siz=8"` // The ID of the tree that contains this node + NumItems uint32 `bin:"off=60, siz=4"` // Number of items + Level uint8 `bin:"off=64, siz=1"` // Level (0 for leaf nodes) + binstruct.End `bin:"off=65"` +} + +type InternalNode struct { + NodeHeader + Body []KeyPointer +} + +type KeyPointer struct { + Key Key `bin:"off=0, siz=11"` + BlockNumber uint64 `bin:"off=11, siz=8"` + Generation Generation `bin:"off=19, siz=8"` + binstruct.End `bin:"off=21"` +} + +type LeafNode struct { + NodeHeader + Body []Item +} + +type Item struct { + Key Key `bin:"off=0, siz=11"` + DataOffset uint32 `bin:"off=11, siz=4"` // relative to the end of the header (0x65) + DataSize uint32 `bin:"off=15, siz=4"` + binstruct.End `bin:"off=19"` +} + +type DevItem struct { + DeviceID ObjID `bin:"off=0, siz=8"` // device ID + + NumBytes uint64 `bin:"off=8, siz=8"` // number of bytes + NumBytesUsed uint64 `bin:"off=10, siz=8"` // number of bytes used + + IOOptimalAlign uint32 `bin:"off=18, siz=4"` // optimal I/O align + IOOptimalWidth uint32 `bin:"off=1c, siz=4"` // optimal I/O width + IOMinSize uint32 `bin:"off=20, siz=4"` // minimal I/O size (sector size) + + Type uint64 `bin:"off=24, siz=8"` // type + Generation Generation `bin:"off=2c, siz=8"` // generation + StartOffset uint64 `bin:"off=34, siz=8"` // start offset + DevGroup uint32 `bin:"off=3c, siz=4"` // dev group + SeekSpeed uint8 `bin:"off=40, siz=1"` // seek speed + Bandwidth uint8 `bin:"off=41, siz=1"` // bandwidth + + DevUUID UUID `bin:"off=42, siz=10"` // device UUID + FSUUID UUID `bin:"off=52, siz=10"` // FS UUID + + binstruct.End `bin:"off=62"` +} + +type Chunk struct { + // Maps logical address to physical. + Size uint64 `bin:"off=0, siz=8"` // size of chunk (bytes) + 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 + IOOptimalWidth uint32 `bin:"off=24, siz=4"` // optimal io width + IoMinSize uint32 `bin:"off=28, siz=4"` // minimal io size (sector size) + 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 []Stripe `bin:"-"` +} + +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 + DeviceUUID UUID `bin:"off=10, siz=10"` // device UUID + binstruct.End `bin:"off=20"` +} diff --git a/pkg/btrfs/types_uuid.go b/pkg/btrfs/types_uuid.go new file mode 100644 index 0000000..b9e3e0c --- /dev/null +++ b/pkg/btrfs/types_uuid.go @@ -0,0 +1,24 @@ +package btrfs + +import ( + "bytes" + "encoding/hex" + "strings" +) + +type UUID [16]byte + +func (uuid UUID) String() string { + str := hex.EncodeToString(uuid[:]) + return strings.Join([]string{ + str[:8], + str[8:12], + str[12:16], + str[16:20], + str[20:32], + }, "-") +} + +func (a UUID) Equal(b UUID) bool { + return bytes.Equal(a[:], b[:]) +} diff --git a/pkg/btrfs/uuid.go b/pkg/btrfs/uuid.go deleted file mode 100644 index b9e3e0c..0000000 --- a/pkg/btrfs/uuid.go +++ /dev/null @@ -1,24 +0,0 @@ -package btrfs - -import ( - "bytes" - "encoding/hex" - "strings" -) - -type UUID [16]byte - -func (uuid UUID) String() string { - str := hex.EncodeToString(uuid[:]) - return strings.Join([]string{ - str[:8], - str[8:12], - str[12:16], - str[16:20], - str[20:32], - }, "-") -} - -func (a UUID) Equal(b UUID) bool { - return bytes.Equal(a[:], b[:]) -} -- cgit v1.2.3-2-g168b