Btrfs filesystem end offset (image size)

btrfsdisk-imagefilesystems

I have a btrfs filesystem of about 7G in a 10G image file img.btrfs (I shrank it using btrfs fi resize -3G /mnt). How can I find the total size (end byte offset) of the filesystem, so that I can shrink the image size? I.e. find out $SIZE for

truncate -s $SIZE img.btrfs

A mechanism that applies to any other filesystem inside an image file would be a plus.

NOTE: one thing that does work is:

INITIAL=$(stat -c %s img.btrfs)
mount img.btrfs /mnt
btrfs fi resize -$NBYTES /mnt
umount /mnt
truncate -s $((INITIAL - NBYTES + 1024*1024)) img.btrfs
mount /img.btrfs /mnt
btrfs fi resize max /mnt

i.e. shrink the btrfs, shrink the image by a little bit less (leaving a 1M overhead), then grow the btrfs to the maximum afforded by the shrunk image.

Best Answer

Annoyingly, btrfs filesystem show returns an approximate value if the size isn't a multiple of 1MB. It also requires a loop device, btrfs filesystem show img.btrfs doesn't work (as of Debian jessie). I can't find another btrfs subcommand that would help.

But file img.btrfs helpfully returns the desired size.

$ truncate -s 16684k /tmp/img.btrfs
$ /sbin/mkfs.btrfs /tmp/img.btrfs
SMALL VOLUME: forcing mixed metadata/data groups
Btrfs v3.17
See http://btrfs.wiki.kernel.org for more information.

Turning ON incompat feature 'mixed-bg': mixed data and metadata block groups
Turning ON incompat feature 'extref': increased hardlink limit per file to 65536
Created a data/metadata chunk of size 1703936
failed to open /dev/btrfs-control skipping device registration: Permission denied
fs created label (null) on /tmp/img.btrfs
        nodesize 4096 leafsize 4096 sectorsize 4096 size 16.29MiB
$ truncate -s 32m /tmp/img.btrfs
$ file /tmp/img.btrfs
/tmp/img.btrfs: BTRFS Filesystem sectorsize 4096, nodesize 4096, leafsize 4096, UUID=61297945-d399-4fdc-ba9f-750ef9f9dfdb, 28672/17084416 bytes used, 1 devices

It directly reads the 8-byte little-endian value at offset 0x10070. If you don't want to parse the output of file, you can extract it. The following POSIX snippet does the job¹:

size_hex=$(cat /tmp/img.btrfs | dd ibs=8 skip=8206 count=1 2>/dev/null | od -tx8 -An | tr abcdef ABCDEF | tr -dc 0-9ABCDEF)
[ ${#size_hex} -eq 16 ] &&
{ echo "ibase=16; $size_hex"; } | bc

or in Perl:

</tmp/btrfs.img perl -e 'seek(STDIN, 0x10070, 0) or sysread(STDIN, $_, 0x10070) == 0x10070 or die "seek"; sysread(STDIN, $_, 8) == 8 or die "read"; print unpack("Q<", $_), "\n"'

file works for some other filesystem types, but that doesn't help much for scripts because the output isn't standardized. I can't think of a generic utility with a standard interface for all common filesystems, maybe some virtualization or forensics tool.

¹ Exercise: why is this a useful use of cat?

Related Question