#!/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 -${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 1 "$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 "$1" "$mountpoint" && return fi shrink "$mountpoint" } btrfs_truncate() { local img="$1" bytes # 548044800/1176715264 bytes used bytes=$(file "$img" | sed -ne 's?.*/\([0-9]*\) bytes used.*?\1?p') if [ "$bytes" ]; then truncate -s "$bytes" "$img" fi } parse_btrfs_dev_size() { local dev="$1" sed -n 's?.* size \([0-9]*\) .* path \([^ ]*\)$?\1 \2? p' | while read size name do [ "$name" = "$dev" ] || continue echo $size break done } get_truncate_size() { local imgfile="$1" mountpoint="$2" loop_dev dev_size loop_dev=$(losetup -ONAME --raw -nj "$imgfile") btrfs fi sh --raw "$mountpoint" | parse_btrfs_dev_size "$loop_dev" } main() { [ "$(id -u)" = 0 ] || die 'you are not root' if [ -d "$1" ]; then 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" mount -t btrfs "$1" "$mountpoint" result=$? if [ $result = 0 ]; then smart_shrink "$mountpoint" result=$? if [ $result = 0 ] then truncate=$(get_truncate_size "$1" "$mountpoint") fi umount "$mountpoint" fi rmdir "$mountpoint" if [ "$truncate" ] then 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