summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/btrfs-dump-tree/ldd.txt198
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--notes.org4
-rw-r--r--pkg/binstruct/size.go11
-rw-r--r--pkg/btrfs/Makefile47
-rw-r--r--pkg/btrfs/btrfsitem/item_chunk.go62
-rw-r--r--pkg/btrfs/btrfsitem/item_dev.go29
-rw-r--r--pkg/btrfs/btrfsitem/item_devextent.go15
-rw-r--r--pkg/btrfs/btrfsitem/item_empty.go9
-rw-r--r--pkg/btrfs/btrfsitem/item_inode.go27
-rw-r--r--pkg/btrfs/btrfsitem/item_inoderef.go12
-rw-r--r--pkg/btrfs/btrfsitem/item_orphan.go9
-rw-r--r--pkg/btrfs/btrfsitem/item_persistent.go19
-rw-r--r--pkg/btrfs/btrfsitem/item_root.go50
-rw-r--r--pkg/btrfs/btrfsitem/item_uuid.go39
-rw-r--r--pkg/btrfs/btrfsitem/items.go20
-rw-r--r--pkg/btrfs/btrfsitem/items.go.bak (renamed from pkg/btrfs/types_item.go)179
-rw-r--r--pkg/btrfs/btrfsitem/items.txt12
-rw-r--r--pkg/btrfs/btrfstyp/misc.go31
-rw-r--r--pkg/btrfs/btrfstyp/objid.go (renamed from pkg/btrfs/types_objid.go)49
-rw-r--r--pkg/btrfs/btrfstyp/uuid.go (renamed from pkg/btrfs/types_uuid.go)2
-rw-r--r--pkg/btrfs/fsck.go3
-rw-r--r--pkg/btrfs/internal/itemtype.go41
-rw-r--r--pkg/btrfs/io1_device.go (renamed from pkg/btrfs/io_device.go)11
-rw-r--r--pkg/btrfs/io2_fs.go (renamed from pkg/btrfs/io_fs.go)140
-rw-r--r--pkg/btrfs/types_bitfields.go120
-rw-r--r--pkg/btrfs/types_btree.go163
-rw-r--r--pkg/btrfs/types_superblock.go (renamed from pkg/btrfs/types_structs.go)175
-rw-r--r--pkg/btrfs/util.go11
-rw-r--r--pkg/util/bitfield.go31
-rw-r--r--pkg/util/int.go3
-rw-r--r--pkg/util/ref.go (renamed from pkg/btrfs/io_ref.go)12
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
diff --git a/go.mod b/go.mod
index 28e854c..738253f 100644
--- a/go.mod
+++ b/go.mod
@@ -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 (
diff --git a/go.sum b/go.sum
index 2dca7c9..e031691 100644
--- a/go.sum
+++ b/go.sum
@@ -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
}