5

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.

  • Is 7GB the total size of the files, or the size of the filesystem (e.g. because this is an image of a disk containing multiple partitions)? – Gilles Jun 20 '15 at 22:58
  • The filesystem initially occupied the entire image file (by virtue of mkfs.btrfs /dev/loop using up all available space). Then I called btrfs fi resize -3G /vol/btrfs. – Anul Jun 22 '15 at 0:35
  • ... so I don't know for sure the exact (byte) size of the filesystem inside the image. But I need to find it out, so that I can truncate the image file appropriately. A mechanism that applies to any other filesystem inside an image file would be a plus. – Anul Jun 22 '15 at 0:46
5

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?

  • great answer. I was thinking parted must have code that does this – Anul Jun 22 '15 at 18:34
  • 1
    @Anul It must have code to do it, but I haven't seen a way to make it spit it out. – Gilles Jun 22 '15 at 19:40
  • I don't think this works: truncate -s 16384987654 /tmp/img.btrfs; mkfs.btrfs /tmp/img.btrfs; file /tmp/img.btrfs reports 114688/16384987136 used. So the number you get from file is not the number you can use in truncate. In this case you need to reserve an additional 518 bytes, but that could vary depending on factors. – Anul Jun 23 '15 at 2:17
  • I've added my current solution -- though it's annoying and error prone. – Anul Jun 23 '15 at 2:26
  • @Anul Your numbers seem fine. On a 16384987654-byte device, btrfs only uses 16384987136 bytes (16384987654 rounded down to a multiple of the block size which is 4kB). The last 518 bytes are unused so you don't need to copy them. – Gilles Jun 23 '15 at 12:15
1

Both btrfs filesystem show and btrfs filesystem usage give you the size of the device, in Mebibytes, Gibibytes, or other powers of 2. You can just pass this on to truncate with the suffix M or G etc as these are also in powers of 2 (do not use suffix MB GB!). For example, on a 1G image reduced by 10M:

sudo btrfs fi show /mnt
 devid    1 size 1014.00MiB used 12.00MiB path /dev/loop0
sudo btrfs fi usage /mnt
 Device size:                1014.00MiB

SIZE=1014M # not MB!
truncate -s $SIZE img.btrfs

There should not be any blocks used beyond this size.

  • 1
    I mentioned this in my answer, but it does rounding to the nearest multiple of 10485.76 bytes. I don't think you can reliably find the filesystem size from that, because the filesystem size can be (AFAIK) any multiple of 4096 bytes. – Gilles Jun 22 '15 at 19:41
  • Thanks @Gilles. I'd forgotten that when I got round to reading the question again. I suppose the easy solution is just to round up a few Mbytes further. – meuh Jun 22 '15 at 20:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.