diff options
33 files changed, 830 insertions, 707 deletions
diff --git a/cmd/btrfs-dump-tree/ldd.txt b/cmd/btrfs-dump-tree/ldd.txt deleted file mode 100644 index 2560bd9..0000000 --- a/cmd/btrfs-dump-tree/ldd.txt +++ /dev/null @@ -1,198 +0,0 @@ -/usr/bin/avahi-discover-standalone: - libgtk-3.so.0 => not found --- -/usr/lib/libavahi-libevent.so: - libevent-2.1.so.7 => not found -/usr/lib/libavahi-libevent.so.1: - libevent-2.1.so.7 => not found -/usr/lib/libavahi-libevent.so.1.0.0: - libevent-2.1.so.7 => not found -/usr/lib/libavahi-qt5.so: - libQt5Core.so.5 => not found -/usr/lib/libavahi-qt5.so.1: - libQt5Core.so.5 => not found -/usr/lib/libavahi-qt5.so.1.0.2: - libQt5Core.so.5 => not found -/usr/lib/libavahi-ui-gtk3.so: - libgtk-3.so.0 => not found - libgdk-3.so.0 => not found -/usr/lib/libavahi-ui-gtk3.so.0: - libgtk-3.so.0 => not found - libgdk-3.so.0 => not found -/usr/lib/libavahi-ui-gtk3.so.0.1.4: - libgtk-3.so.0 => not found - libgdk-3.so.0 => not found --- -/usr/bin/btrfs-convert: - libreiserfscore.so.0 => not found --- -/usr/lib/collectd/amqp.so: - librabbitmq.so.4 => not found - libyajl.so.2 => not found --- -/usr/lib/collectd/ceph.so: - libyajl.so.2 => not found --- -/usr/lib/collectd/connectivity.so: - libyajl.so.2 => not found --- -/usr/lib/collectd/curl_json.so: - libyajl.so.2 => not found --- -/usr/lib/collectd/dbi.so: - libdbi.so.1 => not found --- -/usr/lib/collectd/ipmi.so: - libOpenIPMIpthread.so.0 => not found - libOpenIPMIutils.so.0 => not found - libOpenIPMI.so.0 => not found --- -/usr/lib/collectd/log_logstash.so: - libyajl.so.2 => not found --- -/usr/lib/collectd/memcachec.so: - libmemcached.so.11 => not found --- -/usr/lib/collectd/mqtt.so: - libmosquitto.so.1 => not found --- -/usr/lib/collectd/mysql.so: - libmariadb.so.3 => not found --- -/usr/lib/collectd/notify_desktop.so: - libnotify.so.4 => not found - libgdk_pixbuf-2.0.so.0 => not found -/usr/lib/collectd/notify_email.so: - libesmtp.so.6 => not found --- -/usr/lib/collectd/nut.so: - libupsclient.so.4 => not found --- -/usr/lib/collectd/ovs_events.so: - libyajl.so.2 => not found -/usr/lib/collectd/ovs_stats.so: - libyajl.so.2 => not found --- -/usr/lib/collectd/pinba.so: - libprotobuf-c.so.1 => not found -/usr/lib/collectd/ping.so: - liboping.so.0 => not found -/usr/lib/collectd/postgresql.so: - libpq.so.5 => not found --- -/usr/lib/collectd/procevent.so: - libyajl.so.2 => not found --- -/usr/lib/collectd/snmp.so: - libnetsnmp.so.40 => not found -/usr/lib/collectd/snmp_agent.so: - libnetsnmpagent.so.40 => not found --- -/usr/lib/collectd/sysevent.so: - libyajl.so.2 => not found --- -/usr/lib/collectd/virt.so: - libvirt.so.0 => not found --- -/usr/lib/collectd/write_http.so: - libyajl.so.2 => not found --- -/usr/lib/collectd/write_log.so: - libyajl.so.2 => not found -/usr/lib/collectd/write_prometheus.so: - libprotobuf-c.so.1 => not found --- -/usr/lib/collectd/write_stackdriver.so: - libyajl.so.2 => not found --- -/usr/lib/git-core/git-credential-gnome-keyring: - libgnome-keyring.so.0 => not found --- -/usr/bin/memusagestat: - libgd.so.3 => not found --- -/usr/lib/guile/2.2/extensions/guile-gnutls-v-2.so: - libcrypt.so.1 => not found -/usr/lib/guile/2.2/extensions/guile-gnutls-v-2.so.0: - libcrypt.so.1 => not found -/usr/lib/guile/2.2/extensions/guile-gnutls-v-2.so.0.0.0: - libcrypt.so.1 => not found --- -/usr/bin/gxditview: - libXaw.so.7 => not found - libXmu.so.6 => not found - libXt.so.6 => not found --- -/usr/bin/xtotroff: - libXt.so.6 => not found --- -/usr/bin/grub-mount: - libfuse.so.2 => not found --- -/usr/bin/guile: - libcrypt.so.1 => not found --- -/usr/lib/guile/2.2/extensions/guile-readline.so: - libcrypt.so.1 => not found -/usr/lib/guile/2.2/extensions/guile-readline.so.0: - libcrypt.so.1 => not found -/usr/lib/guile/2.2/extensions/guile-readline.so.0.0.0: - libcrypt.so.1 => not found -/usr/lib/libguile-2.2.so: - libcrypt.so.1 => not found -/usr/lib/libguile-2.2.so.1: - libcrypt.so.1 => not found -/usr/lib/libguile-2.2.so.1.4.1: - libcrypt.so.1 => not found --- -/usr/bin/ftpd: - libcrypt.so.1 => not found --- -/usr/lib/tc/q_atm.so: - libatm.so.1 => not found --- -/usr/bin/make: - libcrypt.so.1 => not found --- -/usr/lib/ssh/ssh-sk-helper: - libfido2.so.1 => not found --- -/usr/bin/pinentry-gnome3: - libgcr-base-3.so.1 => not found -/usr/bin/pinentry-gtk-2: - libgtk-x11-2.0.so.0 => not found - libgdk-x11-2.0.so.0 => not found -/usr/bin/pinentry-qt: - libQt5Widgets.so.5 => not found - libQt5Gui.so.5 => not found - libQt5Core.so.5 => not found --- -/usr/lib/python3.9/lib-dynload/_decimal.cpython-39-x86_64-linux-gnu.so: - libmpdec.so.2 => not found --- -/usr/lib/python3.9/lib-dynload/_tkinter.cpython-39-x86_64-linux-gnu.so: - libtk8.6.so => not found - libtcl8.6.so => not found --- -/usr/lib/python2.7/lib-dynload/_ctypes.so: - libffi.so.8 => not found --- -/usr/lib/python2.7/lib-dynload/_tkinter.so: - libtk8.6.so => not found - libtcl8.6.so => not found --- -/usr/lib/python2.7/lib-dynload/nis.so: - libnsl.so.3 => not found --- -/usr/bin/qemu-keymap: - libxkbcommon.so.0 => not found --- -/usr/lib/lua/5.1/rrd.so: - liblua5.1.so.5.1 => not found -/usr/lib/lua/5.1/rrd.so.0: - liblua5.1.so.5.1 => not found -/usr/lib/lua/5.1/rrd.so.0.0.0: - liblua5.1.so.5.1 => not found --- -/usr/lib/ruby/vendor_ruby/2.7.0/x86_64-linux/RRD.so: - libruby.so.2.7 => not found @@ -5,6 +5,7 @@ go 1.18 require ( github.com/davecgh/go-spew v1.1.0 github.com/stretchr/testify v1.7.1 + golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf ) require ( @@ -5,6 +5,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw= +golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/notes.org b/notes.org deleted file mode 100644 index ba4841e..0000000 --- a/notes.org +++ /dev/null @@ -1,4 +0,0 @@ - - [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/binstruct/size.go b/pkg/binstruct/size.go index 8846c23..0119a7a 100644 --- a/pkg/binstruct/size.go +++ b/pkg/binstruct/size.go @@ -17,7 +17,11 @@ func StaticSize(obj any) int { return sz } -var staticSizerType = reflect.TypeOf((*StaticSizer)(nil)).Elem() +var ( + staticSizerType = reflect.TypeOf((*StaticSizer)(nil)).Elem() + marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() + unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() +) func staticSize(typ reflect.Type) (int, error) { if typ.Implements(staticSizerType) { @@ -41,7 +45,10 @@ func staticSize(typ reflect.Type) (int, error) { } return elemSize * typ.Len(), nil case reflect.Struct: - return getStructHandler(typ).Size, nil + if !(typ.Implements(marshalerType) || typ.Implements(unmarshalerType)) { + return getStructHandler(typ).Size, nil + } + fallthrough default: return 0, fmt.Errorf("type=%v does not implement binfmt.StaticSizer and kind=%v is not a supported statically-sized kind", typ, typ.Kind()) diff --git a/pkg/btrfs/Makefile b/pkg/btrfs/Makefile new file mode 100644 index 0000000..cf3a911 --- /dev/null +++ b/pkg/btrfs/Makefile @@ -0,0 +1,47 @@ +.DEFAULT_GOAL = all +.SECONDARY: +.DELETE_ON_ERROR: + +internal: + mkdir $@ +btrfsitem/items.txt: btrfsitem $(wildcard btrfsitem/item_*.go) $(MAKEFILE_LIST) + sed -En 's,^type (\S+) .* // (.*=.*),\1 \2,p' $(filter btrfsitem/item_%.go,$^) | while read -r typ keys; do for key in $$keys; do echo "$$key" "$$typ"; done; done >$@ +files += btrfsitem/items.txt + +btrfsitem/items.go: btrfsitem/items.txt $(MAKEFILE_LIST) + { \ + echo 'package $(@D)'; \ + echo 'import "lukeshu.com/btrfs-tools/pkg/btrfs/internal"'; \ + echo 'type Type = internal.ItemType'; \ + echo 'const ('; \ + sed -E 's,(.*)=(.*) (.*),\1_KEY=internal.\1_KEY,' $<; \ + echo ')'; \ + } | gofmt >$@ +files += btrfsitem/items.go + +internal/itemtype.go: btrfsitem/items.txt $(MAKEFILE_LIST) + { \ + echo 'package $(@D)'; \ + echo 'import "fmt"'; \ + echo 'type ItemType uint8'; \ + echo 'const ('; \ + sed -E 's,(.*)=(.*) (.*),\1_KEY=ItemType(\2),' $<; \ + echo ')'; \ + echo 'func (t ItemType) String() string {'; \ + echo ' names := map[ItemType]string{'; \ + sed -E 's@(.*)=(.*) (.*)@\1_KEY: "\1",@' $<; \ + echo ' }'; \ + echo ' if name, ok := names[t]; ok {'; \ + echo ' return name'; \ + echo ' }'; \ + echo ' return fmt.Sprintf("%d", t)'; \ + echo '}'; \ + } | gofmt >$@ +files += internal/itemtype.go + +all: $(files) +.PHONY: all + +clean: + rm -f -- $(files) +.PHONY: all diff --git a/pkg/btrfs/btrfsitem/item_chunk.go b/pkg/btrfs/btrfsitem/item_chunk.go new file mode 100644 index 0000000..41706c3 --- /dev/null +++ b/pkg/btrfs/btrfsitem/item_chunk.go @@ -0,0 +1,62 @@ +package btrfsitem + +import ( + "lukeshu.com/btrfs-tools/pkg/binstruct" + "lukeshu.com/btrfs-tools/pkg/btrfs/btrfstyp" +) + +type Chunk struct { // CHUNK_ITEM=228 + // Maps logical address to physical. + Size uint64 `bin:"off=0x0, siz=0x8"` // size of chunk (bytes) + Owner btrfstyp.ObjID `bin:"off=0x8, siz=0x8"` // root referencing this chunk (2) + StripeLen uint64 `bin:"off=0x10, siz=0x8"` // stripe length + Type uint64 `bin:"off=0x18, siz=0x8"` // type (same as flags for block group?) + IOOptimalAlign uint32 `bin:"off=0x20, siz=0x4"` // optimal io alignment + IOOptimalWidth uint32 `bin:"off=0x24, siz=0x4"` // optimal io width + IoMinSize uint32 `bin:"off=0x28, siz=0x4"` // minimal io size (sector size) + NumStripes uint16 `bin:"off=0x2c, siz=0x2"` // number of stripes + SubStripes uint16 `bin:"off=0x2e, siz=0x2"` // sub stripes + binstruct.End `bin:"off=0x30"` + Stripes []ChunkStripe `bin:"-"` +} + +type ChunkStripe struct { + // Stripes follow (for each number of stripes): + DeviceID btrfstyp.ObjID `bin:"off=0, siz=8"` // device ID + Offset uint64 `bin:"off=8, siz=8"` // offset + DeviceUUID btrfstyp.UUID `bin:"off=10, siz=10"` // device UUID + binstruct.End `bin:"off=20"` +} + +func (chunk *Chunk) UnmarshalBinary(dat []byte) (int, error) { + n, err := binstruct.UnmarshalWithoutInterface(dat, chunk) + if err != nil { + return n, err + } + for i := 0; i < int(chunk.NumStripes); i++ { + var stripe ChunkStripe + _n, err := binstruct.Unmarshal(dat[n:], &stripe) + n += _n + if err != nil { + return n, err + } + chunk.Stripes = append(chunk.Stripes, stripe) + } + return n, nil +} + +func (chunk Chunk) MarshalBinary() ([]byte, error) { + chunk.NumStripes = uint16(len(chunk.Stripes)) + ret, err := binstruct.MarshalWithoutInterface(chunk) + if err != nil { + return ret, err + } + for _, stripe := range chunk.Stripes { + _ret, err := binstruct.Marshal(stripe) + ret = append(ret, _ret...) + if err != nil { + return ret, err + } + } + return ret, nil +} diff --git a/pkg/btrfs/btrfsitem/item_dev.go b/pkg/btrfs/btrfsitem/item_dev.go new file mode 100644 index 0000000..f474156 --- /dev/null +++ b/pkg/btrfs/btrfsitem/item_dev.go @@ -0,0 +1,29 @@ +package btrfsitem + +import ( + "lukeshu.com/btrfs-tools/pkg/binstruct" + "lukeshu.com/btrfs-tools/pkg/btrfs/btrfstyp" +) + +type Dev struct { // DEV_ITEM=216 + DeviceID btrfstyp.ObjID `bin:"off=0x0, siz=0x8"` // device ID + + NumBytes uint64 `bin:"off=0x8, siz=0x8"` // number of bytes + NumBytesUsed uint64 `bin:"off=0x10, siz=0x8"` // number of bytes used + + IOOptimalAlign uint32 `bin:"off=0x18, siz=0x4"` // optimal I/O align + IOOptimalWidth uint32 `bin:"off=0x1c, siz=0x4"` // optimal I/O width + IOMinSize uint32 `bin:"off=0x20, siz=0x4"` // minimal I/O size (sector size) + + Type uint64 `bin:"off=0x24, siz=0x8"` // type + Generation btrfstyp.Generation `bin:"off=0x2c, siz=0x8"` // generation + StartOffset uint64 `bin:"off=0x34, siz=0x8"` // start offset + DevGroup uint32 `bin:"off=0x3c, siz=0x4"` // dev group + SeekSpeed uint8 `bin:"off=0x40, siz=0x1"` // seek speed + Bandwidth uint8 `bin:"off=0x41, siz=0x1"` // bandwidth + + DevUUID btrfstyp.UUID `bin:"off=0x42, siz=0x10"` // device UUID + FSUUID btrfstyp.UUID `bin:"off=0x52, siz=0x10"` // FS UUID + + binstruct.End `bin:"off=0x62"` +} diff --git a/pkg/btrfs/btrfsitem/item_devextent.go b/pkg/btrfs/btrfsitem/item_devextent.go new file mode 100644 index 0000000..4bdd1c3 --- /dev/null +++ b/pkg/btrfs/btrfsitem/item_devextent.go @@ -0,0 +1,15 @@ +package btrfsitem + +import ( + "lukeshu.com/btrfs-tools/pkg/binstruct" + "lukeshu.com/btrfs-tools/pkg/btrfs/btrfstyp" +) + +type DevExtent struct { // DEV_EXTENT=204 + ChunkTree int64 `bin:"off=0, siz=8"` + ChunkObjectID btrfstyp.ObjID `bin:"off=8, siz=8"` + ChunkOffset int64 `bin:"off=16, siz=8"` + Length int64 `bin:"off=24, siz=8"` + ChunkTreeUUID btrfstyp.UUID `bin:"off=32, siz=16"` + binstruct.End `bin:"off=48"` +} diff --git a/pkg/btrfs/btrfsitem/item_empty.go b/pkg/btrfs/btrfsitem/item_empty.go new file mode 100644 index 0000000..ed0f66f --- /dev/null +++ b/pkg/btrfs/btrfsitem/item_empty.go @@ -0,0 +1,9 @@ +package btrfsitem + +import ( + "lukeshu.com/btrfs-tools/pkg/binstruct" +) + +type Empty struct { // UNTYPED=0, QGROUP_RELATION=246 + binstruct.End `bin:"off=48"` +} diff --git a/pkg/btrfs/btrfsitem/item_inode.go b/pkg/btrfs/btrfsitem/item_inode.go new file mode 100644 index 0000000..0c7600e --- /dev/null +++ b/pkg/btrfs/btrfsitem/item_inode.go @@ -0,0 +1,27 @@ +package btrfsitem + +import ( + "lukeshu.com/btrfs-tools/pkg/binstruct" + "lukeshu.com/btrfs-tools/pkg/btrfs/btrfstyp" +) + +type Inode struct { // INODE_ITEM=1 + Generation int64 `bin:"off=0x0, siz=0x8"` + TransID int64 `bin:"off=0x8, siz=0x8"` + Size int64 `bin:"off=0x10, siz=0x8"` + NumBytes int64 `bin:"off=0x18, siz=0x8"` + BlockGroup int64 `bin:"off=0x20, siz=0x8"` + NLink int32 `bin:"off=0x28, siz=0x4"` + UID int32 `bin:"off=0x2C, siz=0x4"` + GID int32 `bin:"off=0x30, siz=0x4"` + Mode int32 `bin:"off=0x34, siz=0x4"` + RDev int64 `bin:"off=0x38, siz=0x8"` + Flags uint64 `bin:"off=0x40, siz=0x8"` + Sequence int64 `bin:"off=0x48, siz=0x8"` + Reserved [4]int64 `bin:"off=0x50, siz=0x20"` + ATime btrfstyp.Time `bin:"off=0x70, siz=0xc"` + CTime btrfstyp.Time `bin:"off=0x7c, siz=0xc"` + MTime btrfstyp.Time `bin:"off=0x88, siz=0xc"` + OTime btrfstyp.Time `bin:"off=0x94, siz=0xc"` + binstruct.End `bin:"off=0xa0"` +} diff --git a/pkg/btrfs/btrfsitem/item_inoderef.go b/pkg/btrfs/btrfsitem/item_inoderef.go new file mode 100644 index 0000000..5a271ae --- /dev/null +++ b/pkg/btrfs/btrfsitem/item_inoderef.go @@ -0,0 +1,12 @@ +package btrfsitem + +import ( + "lukeshu.com/btrfs-tools/pkg/binstruct" +) + +type InodeRef struct { // INODE_REF=12 + Index int64 `bin:"off=0x0, siz=0x8"` + NameLen int16 `bin:"off=0x8, siz=0x2"` + binstruct.End `bin:"off=0xa"` + Name []byte `bin:"-"` +} diff --git a/pkg/btrfs/btrfsitem/item_orphan.go b/pkg/btrfs/btrfsitem/item_orphan.go new file mode 100644 index 0000000..6cf29b0 --- /dev/null +++ b/pkg/btrfs/btrfsitem/item_orphan.go @@ -0,0 +1,9 @@ +package btrfsitem + +import ( + "lukeshu.com/btrfs-tools/pkg/binstruct" +) + +type Orphan struct { // ORPHAN_ITEM=48 + binstruct.End `bin:"off=0"` +} diff --git a/pkg/btrfs/btrfsitem/item_persistent.go b/pkg/btrfs/btrfsitem/item_persistent.go new file mode 100644 index 0000000..3221800 --- /dev/null +++ b/pkg/btrfs/btrfsitem/item_persistent.go @@ -0,0 +1,19 @@ +package btrfsitem + +import ( + "lukeshu.com/btrfs-tools/pkg/binstruct" +) + +const ( + DEV_STAT_WRITE_ERRS = iota + DEV_STAT_READ_ERRS + DEV_STAT_FLUSH_ERRS + DEV_STAT_CORRUPTION_ERRS + DEV_STAT_GENERATION_ERRS + DEV_STAT_VALUES_MAX +) + +type DevStats struct { // PERSISTENT_ITEM=249 + Values [DEV_STAT_VALUES_MAX]int64 `bin:"off=0, siz=40"` + binstruct.End `bin:"off=40"` +} diff --git a/pkg/btrfs/btrfsitem/item_root.go b/pkg/btrfs/btrfsitem/item_root.go new file mode 100644 index 0000000..c87a49b --- /dev/null +++ b/pkg/btrfs/btrfsitem/item_root.go @@ -0,0 +1,50 @@ +package btrfsitem + +import ( + "lukeshu.com/btrfs-tools/pkg/binstruct" + "lukeshu.com/btrfs-tools/pkg/btrfs/btrfstyp" + "lukeshu.com/btrfs-tools/pkg/util" +) + +type Root struct { // ROOT_ITEM=132 + Inode Inode `bin:"off=0x0, siz=0xa0"` + Generation int64 `bin:"off=0xa0, siz=0x8"` + RootDirID int64 `bin:"off=0xa8, siz=0x8"` + ByteNr btrfstyp.LogicalAddr `bin:"off=0xb0, siz=0x8"` + ByteLimit int64 `bin:"off=0xb8, siz=0x8"` + BytesUsed int64 `bin:"off=0xc0, siz=0x8"` + LastSnapshot int64 `bin:"off=0xc8, siz=0x8"` + Flags RootFlags `bin:"off=0xd0, siz=0x8"` + Refs int32 `bin:"off=0xd8, siz=0x4"` + DropProgress btrfstyp.Key `bin:"off=0xdc, siz=0x11"` + DropLevel uint8 `bin:"off=0xed, siz=0x1"` + Level uint8 `bin:"off=0xee, siz=0x1"` + GenerationV2 int64 `bin:"off=0xef, siz=0x8"` + UUID btrfstyp.UUID `bin:"off=0xF7, siz=0x10"` + ParentUUID btrfstyp.UUID `bin:"off=0x107, siz=0x10"` + ReceivedUUID btrfstyp.UUID `bin:"off=0x117, siz=0x10"` + CTransID int64 `bin:"off=0x127, siz=0x8"` + OTransID int64 `bin:"off=0x12f, siz=0x8"` + STransID int64 `bin:"off=0x137, siz=0x8"` + RTransID int64 `bin:"off=0x13f, siz=0x8"` + CTime btrfstyp.Time `bin:"off=0x147, siz=0xc"` + OTime btrfstyp.Time `bin:"off=0x153, siz=0xc"` + STime btrfstyp.Time `bin:"off=0x15F, siz=0xc"` + RTime btrfstyp.Time `bin:"off=0x16b, siz=0xc"` + GlobalTreeID btrfstyp.ObjID `bin:"off=0x177, siz=0x8"` + Reserved [7]int64 `bin:"off=0x17f, siz=0x38"` + binstruct.End `bin:"off=0x1b7"` +} + +type RootFlags uint64 + +const ( + BTRFS_ROOT_SUBVOL_RDONLY = RootFlags(1 << iota) +) + +var rootItemFlagNames = []string{ + "SUBVOL_RDONLY", +} + +func (f RootFlags) Has(req RootFlags) bool { return f&req == req } +func (f RootFlags) String() string { return util.BitfieldString(f, rootItemFlagNames) } diff --git a/pkg/btrfs/btrfsitem/item_uuid.go b/pkg/btrfs/btrfsitem/item_uuid.go new file mode 100644 index 0000000..315dd70 --- /dev/null +++ b/pkg/btrfs/btrfsitem/item_uuid.go @@ -0,0 +1,39 @@ +package btrfsitem + +import ( + "lukeshu.com/btrfs-tools/pkg/binstruct" + "lukeshu.com/btrfs-tools/pkg/btrfs/btrfstyp" +) + +// The Key for this item is a UUID, and the item is a list of +// subvolume IDs (ObjectIDs) that that UUID maps to. +type UUIDMap struct { // UUID_SUBVOL=251 UUID_RECEIVED_SUBVOL=252 + SubvolIDs []btrfstyp.ObjID +} + +func (o *UUIDMap) UnmarshalBinary(dat []byte) (int, error) { + o.SubvolIDs = nil + var n int + for len(dat) > n { + var subvolID btrfstyp.ObjID + _n, err := binstruct.Unmarshal(dat[n:], &subvolID) + n += _n + if err != nil { + return n, err + } + o.SubvolIDs = append(o.SubvolIDs, subvolID) + } + return n, nil +} + +func (o UUIDMap) MarshalBinary() ([]byte, error) { + var ret []byte + for _, subvolID := range o.SubvolIDs { + bs, err := binstruct.Marshal(subvolID) + ret = append(ret, bs...) + if err != nil { + return ret, err + } + } + return ret, nil +} diff --git a/pkg/btrfs/btrfsitem/items.go b/pkg/btrfs/btrfsitem/items.go new file mode 100644 index 0000000..e9e03b8 --- /dev/null +++ b/pkg/btrfs/btrfsitem/items.go @@ -0,0 +1,20 @@ +package btrfsitem + +import "lukeshu.com/btrfs-tools/pkg/btrfs/internal" + +type Type = internal.ItemType + +const ( + CHUNK_ITEM_KEY = internal.CHUNK_ITEM_KEY + DEV_ITEM_KEY = internal.DEV_ITEM_KEY + DEV_EXTENT_KEY = internal.DEV_EXTENT_KEY + UNTYPED_KEY = internal.UNTYPED_KEY + QGROUP_RELATION_KEY = internal.QGROUP_RELATION_KEY + INODE_ITEM_KEY = internal.INODE_ITEM_KEY + INODE_REF_KEY = internal.INODE_REF_KEY + ORPHAN_ITEM_KEY = internal.ORPHAN_ITEM_KEY + PERSISTENT_ITEM_KEY = internal.PERSISTENT_ITEM_KEY + ROOT_ITEM_KEY = internal.ROOT_ITEM_KEY + UUID_SUBVOL_KEY = internal.UUID_SUBVOL_KEY + UUID_RECEIVED_SUBVOL_KEY = internal.UUID_RECEIVED_SUBVOL_KEY +) diff --git a/pkg/btrfs/types_item.go b/pkg/btrfs/btrfsitem/items.go.bak index 828ba02..9a91d97 100644 --- a/pkg/btrfs/types_item.go +++ b/pkg/btrfs/btrfsitem/items.go.bak @@ -1,105 +1,103 @@ -package btrfs +package btrfsitem import ( - "fmt" - - "lukeshu.com/btrfs-tools/pkg/binstruct" + "lukeshu.com/btrfs-tools/pkg/btrfs/btrfstyp" ) -type ItemType uint8 +type Type = btrfstyp.ItemType const ( - BTRFS_UNTYPED_KEY = ItemType(0) + BTRFS_UNTYPED_KEY = btrfstyp.ItemType(0) // 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_INODE_ITEM_KEY = btrfstyp.ItemType(1) + BTRFS_INODE_REF_KEY = btrfstyp.ItemType(12) + BTRFS_INODE_EXTREF_KEY = btrfstyp.ItemType(13) + BTRFS_XATTR_ITEM_KEY = btrfstyp.ItemType(24) - BTRFS_VERITY_DESC_ITEM_KEY = ItemType(36) // new - BTRFS_VERITY_MERKLE_ITEM_KEY = ItemType(37) // new + BTRFS_VERITY_DESC_ITEM_KEY = btrfstyp.ItemType(36) // new + BTRFS_VERITY_MERKLE_ITEM_KEY = btrfstyp.ItemType(37) // new - BTRFS_ORPHAN_ITEM_KEY = ItemType(48) + BTRFS_ORPHAN_ITEM_KEY = btrfstyp.ItemType(48) - BTRFS_DIR_LOG_ITEM_KEY = ItemType(60) - BTRFS_DIR_LOG_INDEX_KEY = ItemType(72) + BTRFS_DIR_LOG_ITEM_KEY = btrfstyp.ItemType(60) + BTRFS_DIR_LOG_INDEX_KEY = btrfstyp.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) + BTRFS_DIR_ITEM_KEY = btrfstyp.ItemType(84) + BTRFS_DIR_INDEX_KEY = btrfstyp.ItemType(96) // extent data is for file data - BTRFS_EXTENT_DATA_KEY = ItemType(108) + BTRFS_EXTENT_DATA_KEY = btrfstyp.ItemType(108) // csum items have the checksums for data in the extents - BTRFS_CSUM_ITEM_KEY = ItemType(120) // new + BTRFS_CSUM_ITEM_KEY = btrfstyp.ItemType(120) // new // extent csums are stored in a separate tree and hold csums for // an entire extent on disk. - BTRFS_EXTENT_CSUM_KEY = ItemType(128) + BTRFS_EXTENT_CSUM_KEY = btrfstyp.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) + BTRFS_ROOT_ITEM_KEY = btrfstyp.ItemType(132) // root backrefs tie subvols and snapshots to the directory entries that // reference them - BTRFS_ROOT_BACKREF_KEY = ItemType(144) + BTRFS_ROOT_BACKREF_KEY = btrfstyp.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) + BTRFS_ROOT_REF_KEY = btrfstyp.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) + BTRFS_EXTENT_ITEM_KEY = btrfstyp.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) // new + BTRFS_METADATA_ITEM_KEY = btrfstyp.ItemType(169) // new - BTRFS_TREE_BLOCK_REF_KEY = ItemType(176) + BTRFS_TREE_BLOCK_REF_KEY = btrfstyp.ItemType(176) - BTRFS_EXTENT_DATA_REF_KEY = ItemType(178) + BTRFS_EXTENT_DATA_REF_KEY = btrfstyp.ItemType(178) // old style extent backrefs - BTRFS_EXTENT_REF_V0_KEY = ItemType(180) + BTRFS_EXTENT_REF_V0_KEY = btrfstyp.ItemType(180) - BTRFS_SHARED_BLOCK_REF_KEY = ItemType(182) + BTRFS_SHARED_BLOCK_REF_KEY = btrfstyp.ItemType(182) - BTRFS_SHARED_DATA_REF_KEY = ItemType(184) + BTRFS_SHARED_DATA_REF_KEY = btrfstyp.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) + BTRFS_BLOCK_GROUP_ITEM_KEY = btrfstyp.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) // new + BTRFS_FREE_SPACE_INFO_KEY = btrfstyp.ItemType(198) // new // 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) // new + BTRFS_FREE_SPACE_EXTENT_KEY = btrfstyp.ItemType(199) // new // 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) // new + BTRFS_FREE_SPACE_BITMAP_KEY = btrfstyp.ItemType(200) // new - BTRFS_DEV_EXTENT_KEY = ItemType(204) - BTRFS_DEV_ITEM_KEY = ItemType(216) - BTRFS_CHUNK_ITEM_KEY = ItemType(228) + BTRFS_DEV_EXTENT_KEY = btrfstyp.ItemType(204) + BTRFS_DEV_ITEM_KEY = btrfstyp.ItemType(216) + BTRFS_CHUNK_ITEM_KEY = btrfstyp.ItemType(228) // quota groups - BTRFS_QGROUP_STATUS_KEY = ItemType(240) // new - BTRFS_QGROUP_INFO_KEY = ItemType(242) // new - BTRFS_QGROUP_LIMIT_KEY = ItemType(244) // new - BTRFS_QGROUP_RELATION_KEY = ItemType(246) // new + BTRFS_QGROUP_STATUS_KEY = btrfstyp.ItemType(240) // new + BTRFS_QGROUP_INFO_KEY = btrfstyp.ItemType(242) // new + BTRFS_QGROUP_LIMIT_KEY = btrfstyp.ItemType(244) // new + BTRFS_QGROUP_RELATION_KEY = btrfstyp.ItemType(246) // new // 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. @@ -110,7 +108,7 @@ const ( // // - balance status item // (BTRFS_BALANCE_OBJECTID, BTRFS_TEMPORARY_ITEM_KEY, 0) - BTRFS_TEMPORARY_ITEM_KEY = ItemType(248) // new + BTRFS_TEMPORARY_ITEM_KEY = btrfstyp.ItemType(248) // new // 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 @@ -123,26 +121,27 @@ const ( // - 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) // new + BTRFS_PERSISTENT_ITEM_KEY = btrfstyp.ItemType(249) // new // 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) + BTRFS_DEV_REPLACE_KEY = btrfstyp.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 // new - BTRFS_UUID_KEY_RECEIVED_SUBVOL = ItemType(252) // for UUIDs assigned to received subvols // new + BTRFS_UUID_KEY_SUBVOL = btrfstyp.ItemType(251) // for UUIDs assigned to subvols // new + BTRFS_UUID_KEY_RECEIVED_SUBVOL = btrfstyp.ItemType(252) // for UUIDs assigned to received subvols // new // string items are for debugging. They just store a short string of // data in the FS - BTRFS_STRING_ITEM_KEY = ItemType(253) + BTRFS_STRING_ITEM_KEY = btrfstyp.ItemType(253) ) -func (t ItemType) String() string { - names := map[ItemType]string{ +/* +func (t btrfstyp.ItemType) String() string { + names := map[btrfstyp.ItemType]string{ BTRFS_UNTYPED_KEY: "UNTYPED", BTRFS_INODE_ITEM_KEY: "INODE_ITEM", BTRFS_INODE_REF_KEY: "INODE_REF", @@ -191,84 +190,4 @@ func (t ItemType) String() string { } return fmt.Sprintf("%d", t) } - -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 InodeRefItem struct { - Index int64 `bin:"off=0, siz=8"` - NameLen int16 `bin:"off=8, siz=2"` - binstruct.End `bin:"off=a"` - Name []byte `bin:"-"` -} - -type InodeItem struct { - Generation int64 `bin:"off=0, siz=8"` - TransID int64 `bin:"off=8, siz=8"` - Size int64 `bin:"off=10, siz=8"` - NumBytes int64 `bin:"off=18, siz=8"` - BlockGroup int64 `bin:"off=20, siz=8"` - NLink int32 `bin:"off=28, siz=4"` - UID int32 `bin:"off=2C, siz=4"` - GID int32 `bin:"off=30, siz=4"` - Mode int32 `bin:"off=34, siz=4"` - RDev int64 `bin:"off=38, siz=8"` - Flags uint64 `bin:"off=40, siz=8"` - Sequence int64 `bin:"off=48, siz=8"` - Reserved [4]int64 `bin:"off=50, siz=20"` - ATime Time `bin:"off=70, siz=c"` - CTime Time `bin:"off=7c, siz=c"` - MTime Time `bin:"off=88, siz=c"` - OTime Time `bin:"off=94, siz=c"` - binstruct.End `bin:"off=a0"` -} - -type RootItem struct { - Inode InodeItem `bin:"off=0, siz=a0"` - Generation int64 `bin:"off=a0, siz=8"` - RootDirID int64 `bin:"off=a8, siz=8"` - ByteNr LogicalAddr `bin:"off=b0, siz=8"` - ByteLimit int64 `bin:"off=b8, siz=8"` - BytesUsed int64 `bin:"off=c0, siz=8"` - LastSnapshot int64 `bin:"off=c8, siz=8"` - Flags RootItemFlags `bin:"off=d0, siz=8"` - Refs int32 `bin:"off=d8, siz=4"` - DropProgress Key `bin:"off=dc, siz=11"` - DropLevel uint8 `bin:"off=ed, siz=1"` - Level uint8 `bin:"off=ee, siz=1"` - GenerationV2 int64 `bin:"off=ef, siz=8"` - UUID UUID `bin:"off=F7, siz=10"` - ParentUUID UUID `bin:"off=107, siz=10"` - ReceivedUUID UUID `bin:"off=117, siz=10"` - CTransID int64 `bin:"off=127, siz=8"` - OTransID int64 `bin:"off=12f, siz=8"` - STransID int64 `bin:"off=137, siz=8"` - RTransID int64 `bin:"off=13f, siz=8"` - CTime Time `bin:"off=147, siz=c"` - OTime Time `bin:"off=153, siz=c"` - STime Time `bin:"off=15F, siz=c"` - RTime Time `bin:"off=16b, siz=c"` - GlobalTreeID ObjID `bin:"off=177, siz=8"` - Reserved [7]int64 `bin:"off=17f, siz=38"` - binstruct.End `bin:"off=1b7"` -} +*/ diff --git a/pkg/btrfs/btrfsitem/items.txt b/pkg/btrfs/btrfsitem/items.txt new file mode 100644 index 0000000..02c1d24 --- /dev/null +++ b/pkg/btrfs/btrfsitem/items.txt @@ -0,0 +1,12 @@ +CHUNK_ITEM=228 Chunk +DEV_ITEM=216 Dev +DEV_EXTENT=204 DevExtent +UNTYPED=0, Empty +QGROUP_RELATION=246 Empty +INODE_ITEM=1 Inode +INODE_REF=12 InodeRef +ORPHAN_ITEM=48 Orphan +PERSISTENT_ITEM=249 DevStats +ROOT_ITEM=132 Root +UUID_SUBVOL=251 UUIDMap +UUID_RECEIVED_SUBVOL=252 UUIDMap diff --git a/pkg/btrfs/btrfstyp/misc.go b/pkg/btrfs/btrfstyp/misc.go new file mode 100644 index 0000000..b0847f8 --- /dev/null +++ b/pkg/btrfs/btrfstyp/misc.go @@ -0,0 +1,31 @@ +package btrfstyp + +import ( + "time" + + "lukeshu.com/btrfs-tools/pkg/binstruct" + "lukeshu.com/btrfs-tools/pkg/btrfs/internal" +) + +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 internal.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 uint32 `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)) +} diff --git a/pkg/btrfs/types_objid.go b/pkg/btrfs/btrfstyp/objid.go index bcaac9a..dce8c18 100644 --- a/pkg/btrfs/types_objid.go +++ b/pkg/btrfs/btrfstyp/objid.go @@ -1,13 +1,14 @@ -package btrfs +package btrfstyp import ( "fmt" + + "lukeshu.com/btrfs-tools/pkg/btrfs/internal" + "lukeshu.com/btrfs-tools/pkg/util" ) type ObjID uint64 -const MaxUint64pp = 0x1_00000000_00000000 - const ( // The IDs of the various trees BTRFS_ROOT_TREE_OBJECTID = ObjID(1) // holds pointers to all of the tree roots @@ -18,7 +19,7 @@ const ( 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_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. @@ -26,21 +27,21 @@ const ( 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 + BTRFS_BALANCE_OBJECTID = ObjID(util.MaxUint64pp - 4) // for storing balance parameters in the root tree + BTRFS_ORPHAN_OBJECTID = ObjID(util.MaxUint64pp - 5) // orphan objectid for tracking unlinked/truncated files + BTRFS_TREE_LOG_OBJECTID = ObjID(util.MaxUint64pp - 6) // does write ahead logging to speed up fsyncs + BTRFS_TREE_LOG_FIXUP_OBJECTID = ObjID(util.MaxUint64pp - 7) + BTRFS_TREE_RELOC_OBJECTID = ObjID(util.MaxUint64pp - 8) // space balancing + BTRFS_DATA_RELOC_TREE_OBJECTID = ObjID(util.MaxUint64pp - 9) + BTRFS_EXTENT_CSUM_OBJECTID = ObjID(util.MaxUint64pp - 10) // extent checksums all have this objectid + BTRFS_FREE_SPACE_OBJECTID = ObjID(util.MaxUint64pp - 11) // For storing free space cache + BTRFS_FREE_INO_OBJECTID = ObjID(util.MaxUint64pp - 12) // stores the inode number for the free-ino cache + + BTRFS_MULTIPLE_OBJECTIDS = ObjID(util.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_LAST_FREE_OBJECTID = ObjID(util.MaxUint64pp - 256) BTRFS_FIRST_CHUNK_TREE_OBJECTID = ObjID(256) @@ -51,9 +52,9 @@ const ( BTRFS_EMPTY_SUBVOL_DIR_OBJECTID = ObjID(2) ) -func (id ObjID) Format(typ ItemType) string { +func (id ObjID) Format(typ internal.ItemType) string { switch typ { - case BTRFS_PERSISTENT_ITEM_KEY: + case internal.PERSISTENT_ITEM_KEY: names := map[ObjID]string{ BTRFS_DEV_STATS_OBJECTID: "DEV_STATS", } @@ -61,15 +62,15 @@ func (id ObjID) Format(typ ItemType) string { return name } return fmt.Sprintf("%d", int64(id)) - case BTRFS_DEV_EXTENT_KEY: + case internal.DEV_EXTENT_KEY: return fmt.Sprintf("%d", int64(id)) - case BTRFS_QGROUP_RELATION_KEY: + case internal.QGROUP_RELATION_KEY: return fmt.Sprintf("%d/%d", uint64(id)>>48, uint64(id)&((1<<48)-1)) - case BTRFS_UUID_KEY_SUBVOL, BTRFS_UUID_KEY_RECEIVED_SUBVOL: + case internal.UUID_SUBVOL_KEY, internal.UUID_RECEIVED_SUBVOL_KEY: return fmt.Sprintf("0x%016x", uint64(id)) - case BTRFS_DEV_ITEM_KEY: + case internal.DEV_ITEM_KEY: names := map[ObjID]string{ BTRFS_BALANCE_OBJECTID: "BALANCE", BTRFS_ORPHAN_OBJECTID: "ORPHAN", @@ -88,7 +89,7 @@ func (id ObjID) Format(typ ItemType) string { return name } return fmt.Sprintf("%d", int64(id)) - case BTRFS_CHUNK_ITEM_KEY: + case internal.CHUNK_ITEM_KEY: names := map[ObjID]string{ BTRFS_BALANCE_OBJECTID: "BALANCE", BTRFS_ORPHAN_OBJECTID: "ORPHAN", @@ -140,5 +141,5 @@ func (id ObjID) Format(typ ItemType) string { } func (id ObjID) String() string { - return id.Format(BTRFS_UNTYPED_KEY) + return id.Format(internal.UNTYPED_KEY) } diff --git a/pkg/btrfs/types_uuid.go b/pkg/btrfs/btrfstyp/uuid.go index b9e3e0c..8b0c92e 100644 --- a/pkg/btrfs/types_uuid.go +++ b/pkg/btrfs/btrfstyp/uuid.go @@ -1,4 +1,4 @@ -package btrfs +package btrfstyp import ( "bytes" diff --git a/pkg/btrfs/fsck.go b/pkg/btrfs/fsck.go index b6c80e5..3220a12 100644 --- a/pkg/btrfs/fsck.go +++ b/pkg/btrfs/fsck.go @@ -4,6 +4,7 @@ import ( "fmt" "lukeshu.com/btrfs-tools/pkg/binstruct" + . "lukeshu.com/btrfs-tools/pkg/btrfs/btrfstyp" ) // ScanForNodes mimics btrfs-progs @@ -30,7 +31,7 @@ func ScanForNodes(dev *Device, sb Superblock) error { return fmt.Errorf("sector@%d: %w", pos, err) } var nodeHeader NodeHeader - if err := binstruct.Unmarshal(nodeBuf, &nodeHeader); err != nil { + if _, err := binstruct.Unmarshal(nodeBuf, &nodeHeader); err != nil { return fmt.Errorf("sector@%d: %w", pos, err) } if !nodeHeader.MetadataUUID.Equal(sb.EffectiveMetadataUUID()) { diff --git a/pkg/btrfs/internal/itemtype.go b/pkg/btrfs/internal/itemtype.go new file mode 100644 index 0000000..d780b60 --- /dev/null +++ b/pkg/btrfs/internal/itemtype.go @@ -0,0 +1,41 @@ +package internal + +import "fmt" + +type ItemType uint8 + +const ( + CHUNK_ITEM_KEY = ItemType(228) + DEV_ITEM_KEY = ItemType(216) + DEV_EXTENT_KEY = ItemType(204) + UNTYPED_KEY = ItemType(0) + QGROUP_RELATION_KEY = ItemType(246) + INODE_ITEM_KEY = ItemType(1) + INODE_REF_KEY = ItemType(12) + ORPHAN_ITEM_KEY = ItemType(48) + PERSISTENT_ITEM_KEY = ItemType(249) + ROOT_ITEM_KEY = ItemType(132) + UUID_SUBVOL_KEY = ItemType(251) + UUID_RECEIVED_SUBVOL_KEY = ItemType(252) +) + +func (t ItemType) String() string { + names := map[ItemType]string{ + CHUNK_ITEM_KEY: "CHUNK_ITEM", + DEV_ITEM_KEY: "DEV_ITEM", + DEV_EXTENT_KEY: "DEV_EXTENT", + UNTYPED_KEY: "UNTYPED", + QGROUP_RELATION_KEY: "QGROUP_RELATION", + INODE_ITEM_KEY: "INODE_ITEM", + INODE_REF_KEY: "INODE_REF", + ORPHAN_ITEM_KEY: "ORPHAN_ITEM", + PERSISTENT_ITEM_KEY: "PERSISTENT_ITEM", + ROOT_ITEM_KEY: "ROOT_ITEM", + UUID_SUBVOL_KEY: "UUID_SUBVOL", + UUID_RECEIVED_SUBVOL_KEY: "UUID_RECEIVED_SUBVOL", + } + if name, ok := names[t]; ok { + return name + } + return fmt.Sprintf("%d", t) +} diff --git a/pkg/btrfs/io_device.go b/pkg/btrfs/io1_device.go index 042c9f2..ed418a0 100644 --- a/pkg/btrfs/io_device.go +++ b/pkg/btrfs/io1_device.go @@ -3,6 +3,9 @@ package btrfs import ( "fmt" "os" + + . "lukeshu.com/btrfs-tools/pkg/btrfs/btrfstyp" + "lukeshu.com/btrfs-tools/pkg/util" ) type Device struct { @@ -27,7 +30,7 @@ func (dev *Device) ReadAt(dat []byte, paddr PhysicalAddr) (int, error) { return dev.File.ReadAt(dat, int64(paddr)) } -func (dev *Device) Superblocks() ([]Ref[PhysicalAddr, Superblock], error) { +func (dev *Device) Superblocks() ([]util.Ref[PhysicalAddr, Superblock], error) { const superblockSize = 0x1000 sz, err := dev.Size() @@ -35,10 +38,10 @@ func (dev *Device) Superblocks() ([]Ref[PhysicalAddr, Superblock], error) { return nil, err } - var ret []Ref[PhysicalAddr, Superblock] + var ret []util.Ref[PhysicalAddr, Superblock] for i, addr := range superblockAddrs { if addr+superblockSize <= sz { - superblock := Ref[PhysicalAddr, Superblock]{ + superblock := util.Ref[PhysicalAddr, Superblock]{ File: dev, Addr: addr, } @@ -54,7 +57,7 @@ func (dev *Device) Superblocks() ([]Ref[PhysicalAddr, Superblock], error) { return ret, nil } -func (dev *Device) Superblock() (ret Ref[PhysicalAddr, Superblock], err error) { +func (dev *Device) Superblock() (ret util.Ref[PhysicalAddr, Superblock], err error) { sbs, err := dev.Superblocks() if err != nil { return ret, err diff --git a/pkg/btrfs/io_fs.go b/pkg/btrfs/io2_fs.go index 4f52d86..9b1e717 100644 --- a/pkg/btrfs/io_fs.go +++ b/pkg/btrfs/io2_fs.go @@ -6,6 +6,9 @@ import ( "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 { @@ -36,8 +39,8 @@ func (fs *FS) Size() (LogicalAddr, error) { return ret, nil } -func (fs *FS) Superblocks() ([]Ref[PhysicalAddr, Superblock], error) { - var ret []Ref[PhysicalAddr, Superblock] +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 { @@ -48,7 +51,7 @@ func (fs *FS) Superblocks() ([]Ref[PhysicalAddr, Superblock], error) { return ret, nil } -func (fs *FS) Superblock() (ret Ref[PhysicalAddr, Superblock], err error) { +func (fs *FS) Superblock() (ret util.Ref[PhysicalAddr, Superblock], err error) { sbs, err := fs.Superblocks() if err != nil { return ret, err @@ -122,21 +125,15 @@ func (fs *FS) Init() error { 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 { + if _, err := binstruct.Unmarshal(dat, &pair.Chunk); err != nil { return err } - dat = dat[0x30:] - for i := 0; i < int(pair.Chunk.NumStripes); i++ { - var stripe Stripe - if err := binstruct.Unmarshal(dat, &stripe); err != nil { - return err - } - pair.Chunk.Stripes = append(pair.Chunk.Stripes, stripe) - dat = dat[0x20:] - } fs.chunks = append(fs.chunks, pair) return nil }); err != nil { @@ -207,96 +204,51 @@ func (fs *FS) maybeShortReadAt(dat []byte, laddr LogicalAddr) (int, error) { return len(dat), nil } -func (fs *FS) ReadNode(addr LogicalAddr) (Node, error) { +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 nil, err + return ret, err } nodeBuf := make([]byte, sb.Data.NodeSize) if _, err := fs.ReadAt(nodeBuf, addr); err != nil { - return nil, err + return ret, err } - var nodeHeader NodeHeader - if err := binstruct.Unmarshal(nodeBuf, &nodeHeader); err != nil { - return nil, fmt.Errorf("node@%d: %w", addr, 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) } - if !nodeHeader.MetadataUUID.Equal(sb.Data.EffectiveMetadataUUID()) { - return nil, fmt.Errorf("node@%d: does not look like a node", addr) + // sanity checking + + if !node.Head.MetadataUUID.Equal(sb.Data.EffectiveMetadataUUID()) { + return ret, fmt.Errorf("node@%d: does not look like a node", addr) } - if nodeHeader.Addr != addr { - return nil, fmt.Errorf("node@%d: read from laddr=%d but claims to be at laddr=%d", - addr, addr, nodeHeader.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 := nodeHeader.Checksum - calced := CRC32c(nodeBuf[0x20:]) + stored := node.Head.Checksum + calced := CRC32c(nodeBuf[binstruct.StaticSize(CSum{}):]) if !calced.Equal(stored) { - return nil, fmt.Errorf("node@%d: checksum mismatch: stored=%s calculated=%s", + return ret, fmt.Errorf("node@%d: checksum mismatch: stored=%s calculated=%s", addr, stored, calced) } - nodeHeader.Size = sb.Data.NodeSize + // return - if nodeHeader.Level > 0 { - // internal node - nodeHeader.MaxItems = (sb.Data.NodeSize - 0x65) / 0x21 - ret := &InternalNode{ - Header: Ref[LogicalAddr, NodeHeader]{ - File: fs, - Addr: addr, - Data: nodeHeader, - }, - Body: nil, - } - for i := uint32(0); i < nodeHeader.NumItems; i++ { - itemOff := 0x65 + (0x21 * int(i)) - var item KeyPointer - if err := binstruct.Unmarshal(nodeBuf[itemOff:], &item); err != nil { - return nil, fmt.Errorf("node@%d (internal): item %d: %w", addr, i, err) - } - ret.Body = append(ret.Body, Ref[LogicalAddr, KeyPointer]{ - File: fs, - Addr: addr + LogicalAddr(itemOff), - Data: item, - }) - } - return ret, nil - } else { - // leaf node - nodeHeader.MaxItems = (sb.Data.NodeSize - 0x65) / 0x19 - ret := &LeafNode{ - Header: Ref[LogicalAddr, NodeHeader]{ - File: fs, - Addr: addr, - Data: nodeHeader, - }, - Body: nil, - } - for i := uint32(0); i < nodeHeader.NumItems; i++ { - itemOff := 0x65 + (0x19 * int(i)) - var item Item - if err := binstruct.Unmarshal(nodeBuf[itemOff:], &item); err != nil { - return nil, fmt.Errorf("node@%d (leaf): item %d: %w", addr, i, err) - } - dataOff := 0x65 + int(item.DataOffset) - dataSize := int(item.DataSize) - item.Data = Ref[LogicalAddr, []byte]{ - File: fs, - Addr: addr + LogicalAddr(dataOff), - Data: nodeBuf[dataOff : dataOff+dataSize], - } - ret.Body = append(ret.Body, Ref[LogicalAddr, Item]{ - File: fs, - Addr: addr + LogicalAddr(itemOff), - Data: item, - }) - } - return ret, nil - } + return util.Ref[LogicalAddr, Node]{ + File: fs, + Addr: addr, + Data: node, + }, nil } func (fs *FS) WalkTree(nodeAddr LogicalAddr, fn func(Key, []byte) error) error { @@ -307,19 +259,15 @@ func (fs *FS) WalkTree(nodeAddr LogicalAddr, fn func(Key, []byte) error) error { if err != nil { return err } - switch node := node.(type) { - case *InternalNode: - for _, item := range node.Body { - // fn(item.Data.Key, TODO) - if err := fs.WalkTree(item.Data.BlockPtr, fn); 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 } - case *LeafNode: - for _, item := range node.Body { - if err := fn(item.Data.Key, item.Data.Data.Data); err != nil { - return err - } + } + for _, item := range node.Data.BodyLeaf { + if err := fn(item.Head.Key, item.Body); err != nil { + return err } } return nil diff --git a/pkg/btrfs/types_bitfields.go b/pkg/btrfs/types_bitfields.go deleted file mode 100644 index 391ac15..0000000 --- a/pkg/btrfs/types_bitfields.go +++ /dev/null @@ -1,120 +0,0 @@ -package btrfs - -import ( - "encoding/binary" - "fmt" - "strings" - - "lukeshu.com/btrfs-tools/pkg/binstruct" -) - -func bitfieldString[T ~uint8 | ~uint16 | ~uint32 | ~uint64](bitfield T, bitnames []string) string { - var out strings.Builder - fmt.Fprintf(&out, "0x%0X", uint64(bitfield)) - if bitfield == 0 { - out.WriteString("(none)") - } else { - rest := bitfield - sep := '(' - for i := 0; rest != 0; i++ { - if rest&(1<<i) != 0 { - out.WriteRune(sep) - if i < len(bitnames) { - out.WriteString(bitnames[i]) - } else { - fmt.Fprintf(&out, "(1<<%d)", i) - } - sep = '|' - } - rest &^= 1 << i - } - out.WriteRune(')') - } - return out.String() -} - -type IncompatFlags uint64 - -const ( - FeatureIncompatMixedBackref = IncompatFlags(1 << iota) - FeatureIncompatDefaultSubvol - FeatureIncompatMixedGroups - FeatureIncompatCompressLZO - FeatureIncompatCompressZSTD - FeatureIncompatBigMetadata // buggy - FeatureIncompatExtendedIRef - FeatureIncompatRAID56 - FeatureIncompatSkinnyMetadata - FeatureIncompatNoHoles - FeatureIncompatMetadataUUID - FeatureIncompatRAID1C34 - FeatureIncompatZoned - FeatureIncompatExtentTreeV2 -) - -var incompatFlagNames = []string{ - "FeatureIncompatMixedBackref", - "FeatureIncompatDefaultSubvol", - "FeatureIncompatMixedGroups", - "FeatureIncompatCompressLZO", - "FeatureIncompatCompressZSTD", - "FeatureIncompatBigMetadata ", - "FeatureIncompatExtendedIRef", - "FeatureIncompatRAID56", - "FeatureIncompatSkinnyMetadata", - "FeatureIncompatNoHoles", - "FeatureIncompatMetadataUUID", - "FeatureIncompatRAID1C34", - "FeatureIncompatZoned", - "FeatureIncompatExtentTreeV2", -} - -func (f IncompatFlags) Has(req IncompatFlags) bool { return f&req == req } -func (f IncompatFlags) String() string { return bitfieldString(f, incompatFlagNames) } - -type NodeFlags uint64 - -func (NodeFlags) BinarySize() int64 { - return 7 -} -func (f NodeFlags) MarshalBinary() []byte { - var bs [8]byte - binary.LittleEndian.PutUint64(bs[:], uint64(f)) - return bs[:7] -} -func (f *NodeFlags) UnmarshalBinary(dat []byte) { - var bs [8]byte - copy(bs[:7], dat[:7]) - *f = NodeFlags(binary.LittleEndian.Uint64(bs[:])) -} - -var ( - _ binstruct.Marshaler = NodeFlags(0) - _ binstruct.Unmarshaler = (*NodeFlags)(nil) -) - -const ( - NodeWritten = NodeFlags(1 << iota) - NodeReloc -) - -var nodeFlagNames = []string{ - "WRITTEN", - "RELOC", -} - -func (f NodeFlags) Has(req NodeFlags) bool { return f&req == req } -func (f NodeFlags) String() string { return bitfieldString(f, nodeFlagNames) } - -type RootItemFlags uint64 - -const ( - BTRFS_ROOT_SUBVOL_RDONLY = RootItemFlags(1 << iota) -) - -var rootItemFlagNames = []string{ - "SUBVOL_RDONLY", -} - -func (f RootItemFlags) Has(req RootItemFlags) bool { return f&req == req } -func (f RootItemFlags) String() string { return bitfieldString(f, rootItemFlagNames) } diff --git a/pkg/btrfs/types_btree.go b/pkg/btrfs/types_btree.go new file mode 100644 index 0000000..2807cee --- /dev/null +++ b/pkg/btrfs/types_btree.go @@ -0,0 +1,163 @@ +package btrfs + +import ( + "encoding/binary" + "fmt" + + "lukeshu.com/btrfs-tools/pkg/binstruct" + . "lukeshu.com/btrfs-tools/pkg/btrfs/btrfstyp" + "lukeshu.com/btrfs-tools/pkg/util" +) + +type Node struct { + // Some context from the parent filesystem + Size uint32 // superblock.NodeSize + + // The node's header (always present) + Head NodeHeader + + // The node's body (which one of these is present depends on + // the node's type, as specified in the header) + BodyInternal []KeyPointer // for internal nodes + BodyLeaf []Item // for leave nodes +} + +type NodeHeader struct { + Checksum CSum `bin:"off=0x0, siz=0x20"` // Checksum of everything after this field (from 20 to the end of the node) + MetadataUUID UUID `bin:"off=0x20, siz=0x10"` // FS UUID + Addr LogicalAddr `bin:"off=0x30, siz=0x8"` // Logical address of this node + Flags NodeFlags `bin:"off=0x38, siz=0x7"` + BackrefRev uint8 `bin:"off=0x3f, siz=0x1"` + ChunkTreeUUID UUID `bin:"off=0x40, siz=0x10"` // Chunk tree UUID + Generation Generation `bin:"off=0x50, siz=0x8"` // Generation + Owner ObjID `bin:"off=0x58, siz=0x8"` // The ID of the tree that contains this node + NumItems uint32 `bin:"off=0x60, siz=0x4"` // Number of items + Level uint8 `bin:"off=0x64, siz=0x1"` // Level (0 for leaf nodes) + binstruct.End `bin:"off=0x65"` +} + +type NodeFlags uint64 + +func (NodeFlags) BinaryStaticSize() int { + return 7 +} +func (f NodeFlags) MarshalBinary() ([]byte, error) { + var bs [8]byte + binary.LittleEndian.PutUint64(bs[:], uint64(f)) + return bs[:7], nil +} +func (f *NodeFlags) UnmarshalBinary(dat []byte) (int, error) { + var bs [8]byte + copy(bs[:7], dat[:7]) + *f = NodeFlags(binary.LittleEndian.Uint64(bs[:])) + return 7, nil +} + +var ( + _ binstruct.StaticSizer = NodeFlags(0) + _ binstruct.Marshaler = NodeFlags(0) + _ binstruct.Unmarshaler = (*NodeFlags)(nil) +) + +const ( + NodeWritten = NodeFlags(1 << iota) + NodeReloc +) + +var nodeFlagNames = []string{ + "WRITTEN", + "RELOC", +} + +func (f NodeFlags) Has(req NodeFlags) bool { return f&req == req } +func (f NodeFlags) String() string { return util.BitfieldString(f, nodeFlagNames) } + +type KeyPointer struct { + Key Key `bin:"off=0, siz=11"` + BlockPtr LogicalAddr `bin:"off=11, siz=8"` + Generation Generation `bin:"off=19, siz=8"` + binstruct.End `bin:"off=21"` +} + +type ItemHeader 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 Item struct { + Head ItemHeader + Body []byte +} + +// MaxItems returns the maximum possible valid value of +// .Haad.NumItems. +func (node Node) MaxItems() uint32 { + bodyBytes := node.Size - uint32(binstruct.StaticSize(NodeHeader{})) + if node.Head.Level > 0 { + return bodyBytes / uint32(binstruct.StaticSize(KeyPointer{})) + } else { + return bodyBytes / uint32(binstruct.StaticSize(ItemHeader{})) + } +} + +func (node *Node) UnmarshalBinary(nodeBuf []byte) (int, error) { + n, err := binstruct.Unmarshal(nodeBuf, &node.Head) + if err != nil { + return n, err + } + if node.Head.Level > 0 { + // internal node + for i := uint32(0); i < node.Head.NumItems; i++ { + var item KeyPointer + _n, err := binstruct.Unmarshal(nodeBuf[n:], &item) + n += _n + if err != nil { + return n, fmt.Errorf("(internal): item %d: %w", i, err) + } + node.BodyInternal = append(node.BodyInternal, item) + } + return n, nil + } else { + // leaf node + lastRead := 0 + for i := uint32(0); i < node.Head.NumItems; i++ { + var item Item + _n, err := binstruct.Unmarshal(nodeBuf[n:], &item.Head) + n += _n + if err != nil { + return n, fmt.Errorf("(leaf): item %d: %w", i, err) + } + + dataOff := binstruct.StaticSize(NodeHeader{}) + int(item.Head.DataOffset) + dataSize := int(item.Head.DataSize) + if dataOff+dataSize > len(nodeBuf) { + return max(n, lastRead), fmt.Errorf("(leaf): item references byte %d, but node only has %d bytes", + dataOff+dataSize, len(nodeBuf)) + } + lastRead = max(lastRead, dataOff+dataSize) + item.Body = nodeBuf[dataOff : dataOff+dataSize] + + node.BodyLeaf = append(node.BodyLeaf, item) + } + return max(n, lastRead), nil + } +} + +func (node Node) MarshalBinary() ([]byte, error) { + panic("TODO") +} + +func (node *Node) LeafFreeSpace() uint32 { + if node.Head.Level > 0 { + panic(fmt.Errorf("Node.LeafFreeSpace: not a leaf node")) + } + freeSpace := node.Size + freeSpace -= uint32(binstruct.StaticSize(NodeHeader{})) + for _, item := range node.BodyLeaf { + freeSpace -= uint32(binstruct.StaticSize(ItemHeader{})) + freeSpace -= item.Head.DataSize + } + return freeSpace +} diff --git a/pkg/btrfs/types_structs.go b/pkg/btrfs/types_superblock.go index bfaa0e6..1b69c03 100644 --- a/pkg/btrfs/types_structs.go +++ b/pkg/btrfs/types_superblock.go @@ -3,34 +3,13 @@ package btrfs import ( "fmt" "reflect" - "time" "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 ( - 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 uint32 `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 @@ -65,10 +44,10 @@ type Superblock struct { 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 + DevItem btrfsitem.Dev `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"` @@ -130,9 +109,9 @@ func (sb Superblock) EffectiveMetadataUUID() UUID { } type SysChunk struct { - Key `bin:"off=0, siz=11"` - Chunk `bin:"off=11, siz=30"` - binstruct.End `bin:"off=41"` + Key `bin:"off=0, siz=11"` + btrfsitem.Chunk `bin:"off=11, siz=30"` + binstruct.End `bin:"off=41"` } func (sb Superblock) ParseSysChunkArray() ([]SysChunk, error) { @@ -140,18 +119,20 @@ func (sb Superblock) ParseSysChunkArray() ([]SysChunk, error) { var ret []SysChunk for len(dat) > 0 { var pair SysChunk - if err := binstruct.Unmarshal(dat, &pair); err != nil { + n, err := binstruct.Unmarshal(dat, &pair) + dat = dat[n:] + if 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 { + var stripe btrfsitem.ChunkStripe + n, err := binstruct.Unmarshal(dat, &stripe) + dat = dat[n:] + if err != nil { return nil, err } pair.Chunk.Stripes = append(pair.Chunk.Stripes, stripe) - dat = dat[0x20:] } ret = append(ret, pair) @@ -195,89 +176,41 @@ type RootBackup struct { binstruct.End `bin:"off=a8"` } -type Node interface { - GetNodeHeader() Ref[LogicalAddr, NodeHeader] -} - -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 NodeFlags `bin:"off=38, siz=7"` - BackrefRev uint8 `bin:"off=3f, siz=1"` - ChunkTreeUUID UUID `bin:"off=40, siz=10"` // Chunk tree UUID - Generation Generation `bin:"off=50, siz=8"` // Generation - Owner ObjID `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"` - - Size uint32 `bin:"-"` // superblock.NodeSize - MaxItems uint32 `bin:"-"` // Maximum possible value of NumItems -} - -type InternalNode struct { - Header Ref[LogicalAddr, NodeHeader] - Body []Ref[LogicalAddr, KeyPointer] -} - -func (in *InternalNode) GetNodeHeader() Ref[LogicalAddr, NodeHeader] { - return in.Header -} - -type KeyPointer struct { - Key Key `bin:"off=0, siz=11"` - BlockPtr LogicalAddr `bin:"off=11, siz=8"` - Generation Generation `bin:"off=19, siz=8"` - binstruct.End `bin:"off=21"` -} - -type LeafNode struct { - Header Ref[LogicalAddr, NodeHeader] - Body []Ref[LogicalAddr, Item] -} - -func (ln *LeafNode) GetNodeHeader() Ref[LogicalAddr, NodeHeader] { - return ln.Header -} - -func (ln *LeafNode) FreeSpace() uint32 { - freeSpace := ln.Header.Data.Size - freeSpace -= 0x65 - for _, item := range ln.Body { - freeSpace -= 0x19 - freeSpace -= item.Data.DataSize - } - return freeSpace -} - -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"` - Data Ref[LogicalAddr, []byte] `bin:"-"` -} - -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 IncompatFlags uint64 + +const ( + FeatureIncompatMixedBackref = IncompatFlags(1 << iota) + FeatureIncompatDefaultSubvol + FeatureIncompatMixedGroups + FeatureIncompatCompressLZO + FeatureIncompatCompressZSTD + FeatureIncompatBigMetadata // buggy + FeatureIncompatExtendedIRef + FeatureIncompatRAID56 + FeatureIncompatSkinnyMetadata + FeatureIncompatNoHoles + FeatureIncompatMetadataUUID + FeatureIncompatRAID1C34 + FeatureIncompatZoned + FeatureIncompatExtentTreeV2 +) -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"` -} +var incompatFlagNames = []string{ + "FeatureIncompatMixedBackref", + "FeatureIncompatDefaultSubvol", + "FeatureIncompatMixedGroups", + "FeatureIncompatCompressLZO", + "FeatureIncompatCompressZSTD", + "FeatureIncompatBigMetadata ", + "FeatureIncompatExtendedIRef", + "FeatureIncompatRAID56", + "FeatureIncompatSkinnyMetadata", + "FeatureIncompatNoHoles", + "FeatureIncompatMetadataUUID", + "FeatureIncompatRAID1C34", + "FeatureIncompatZoned", + "FeatureIncompatExtentTreeV2", +} + +func (f IncompatFlags) Has(req IncompatFlags) bool { return f&req == req } +func (f IncompatFlags) String() string { return util.BitfieldString(f, incompatFlagNames) } diff --git a/pkg/btrfs/util.go b/pkg/btrfs/util.go index 04462f8..312ec75 100644 --- a/pkg/btrfs/util.go +++ b/pkg/btrfs/util.go @@ -1,5 +1,9 @@ package btrfs +import ( + "golang.org/x/exp/constraints" +) + func inSlice[T comparable](needle T, haystack []T) bool { for _, straw := range haystack { if needle == straw { @@ -8,3 +12,10 @@ func inSlice[T comparable](needle T, haystack []T) bool { } return false } + +func max[T constraints.Ordered](a, b T) T { + if a > b { + return a + } + return b +} diff --git a/pkg/util/bitfield.go b/pkg/util/bitfield.go new file mode 100644 index 0000000..5e2ba06 --- /dev/null +++ b/pkg/util/bitfield.go @@ -0,0 +1,31 @@ +package util + +import ( + "fmt" + "strings" +) + +func BitfieldString[T ~uint8 | ~uint16 | ~uint32 | ~uint64](bitfield T, bitnames []string) string { + var out strings.Builder + fmt.Fprintf(&out, "0x%0X", uint64(bitfield)) + if bitfield == 0 { + out.WriteString("(none)") + } else { + rest := bitfield + sep := '(' + for i := 0; rest != 0; i++ { + if rest&(1<<i) != 0 { + out.WriteRune(sep) + if i < len(bitnames) { + out.WriteString(bitnames[i]) + } else { + fmt.Fprintf(&out, "(1<<%d)", i) + } + sep = '|' + } + rest &^= 1 << i + } + out.WriteRune(')') + } + return out.String() +} diff --git a/pkg/util/int.go b/pkg/util/int.go new file mode 100644 index 0000000..fab553d --- /dev/null +++ b/pkg/util/int.go @@ -0,0 +1,3 @@ +package util + +const MaxUint64pp = 0x1_00000000_00000000 diff --git a/pkg/btrfs/io_ref.go b/pkg/util/ref.go index a91b691..69f7db4 100644 --- a/pkg/btrfs/io_ref.go +++ b/pkg/util/ref.go @@ -1,4 +1,4 @@ -package btrfs +package util import ( "lukeshu.com/btrfs-tools/pkg/binstruct" @@ -17,13 +17,13 @@ type Ref[A ~int64, T any] struct { } func (r *Ref[A, T]) Read() error { - size, err := binstruct.Size(r.Data) - if err != nil { - return err - } + size := binstruct.StaticSize(r.Data) buf := make([]byte, size) if _, err := r.File.ReadAt(buf, r.Addr); err != nil { return err } - return binstruct.Unmarshal(buf, &r.Data) + if _, err := binstruct.Unmarshal(buf, &r.Data); err != nil { + return err + } + return nil } |