#!/bin/sh die() { printf "%s: Error: %s\n" "$0" "$*" >&2; exit 1; } shrink() { local shrinkmegs=100 mountpoint="$1" while true; do while ! btrfs filesystem resize "${DEVICE_ID}:-${shrinkmegs}M" "$mountpoint"/; do shrinkmegs=$((shrinkmegs - 10 )) if [ $shrinkmegs -lt 10 ]; then return fi done done } smart_shrink() { local min_dev_size mountpoint="$1" if min_dev_size=$(btrfs inspect-internal min-dev-size --id "$DEVICE_ID" "$mountpoint") && [ "$min_dev_size" ] then set -- $min_dev_size [ "$2" = bytes ] || die "Unexpected output from btrfs inspect-internal min-dev-size: '$min_dev_size'" btrfs filesystem resize "$DEVICE_ID:$1" "$mountpoint" && return fi shrink "$mountpoint" } parse_btrfs_dev() { local dev="$1" sed -n 's?^ devid *\([0-9]*\) size \([0-9]*\) .* path \([^ ]*\)$?\1 \2 \3? p' | while read devid size name do [ "$name" = "$dev" ] || continue echo "$devid $size" break done } get_btrfs_dev() { local imgfile="$1" mountpoint="$2" loop_dev dev_size losetup -nj "$imgfile" >&2 loop_dev=$(losetup -ONAME --raw -nj "$imgfile") ds=$(btrfs fi sh --raw "$mountpoint" | parse_btrfs_dev "$loop_dev") [ "$ds" ] || return DEVICE_ID=${ds% *} DEVICE_SIZE=${ds#* } } main() { [ "$(id -u)" = 0 ] || die 'you are not root' if [ -d "$1" ]; then exit 1 mountpoint=$1 mountpoint -q "$mountpoint" || die "not a mountpoint: $1" smart_shrink "$mountpoint" elif [ -f "$1" ]; then truncate= mountpoint="$1".mnt.tmp mkdir -p "$mountpoint" btrfs dev scan -u mount -t btrfs "$1" "$mountpoint" result=$? if [ $result = 0 ]; then get_btrfs_dev "$1" "$mountpoint" && smart_shrink "$mountpoint" result=$? if [ $result = 0 ] then get_btrfs_dev "$1" "$mountpoint" && truncate=$DEVICE_SIZE fi umount "$mountpoint" fi rmdir "$mountpoint" if [ "$truncate" ] then r=$((truncate % 4096)) if [ $r -ne 0 ] then truncate=$((truncate + 4096 - r)) fi truncate -s "$truncate" "$1" fi return $result else die "not a file or directory: $1" fi } usage() { echo "Usage: $0 " >&2 } case $# in 0) usage ;; 1) main "$1" ;; *) usage; exit 1 ;; esac