summaryrefslogtreecommitdiff
path: root/btrfs-shrink
blob: 1d059ff42beca4e9009f88ae89fb258e30fd79f4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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 <mountpoint>" >&2
}

case $# in
    0) usage ;;
    1) main "$1" ;;
    *) usage; exit 1 ;;
esac