From f6e7c471f5a6d9fb1c9defc514d3b668a9cb9f8f Mon Sep 17 00:00:00 2001 From: Andrew Cady Date: Mon, 12 Oct 2020 14:59:59 -0400 Subject: btrfs-shrink --- btrfs-shrink | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100755 btrfs-shrink diff --git a/btrfs-shrink b/btrfs-shrink new file mode 100755 index 0000000..1d059ff --- /dev/null +++ b/btrfs-shrink @@ -0,0 +1,101 @@ +#!/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 -- cgit v1.2.3