-
Notifications
You must be signed in to change notification settings - Fork 0
/
common.sh
191 lines (165 loc) · 4.8 KB
/
common.sh
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# © 2015–2016 Sebastián González
# http://www.apache.org/licenses/LICENSE-2.0
# Common code for btrfs-backup scripts.
# Inspired on
# http://marc.merlins.org/linux/scripts/btrfs-subvolume-backup
set -o nounset
set -o errexit
set -o pipefail
[ -v DEBUG ] && set -o xtrace
: ${script:=${0##*/}} # shortcut for `basename $0`.
: ${this:=$(realpath "$0")}
: ${lockfile:=.btrfs-backup-lock}
: ${mountfile:=.btrfs-backup-mount-point}
die() {
# Don't loop on ERR.
trap '' ERR
# Process options.
TEMP=$(getopt --longoptions line:,status: -o l:,s: -- "$@")
eval set -- $TEMP
while true; do
case "$1" in
--line|-l)
shift
line=$1
shift
;;
--status|-s)
shift
status=$1
shift
;;
--)
shift
break
;;
*)
echo "Unexpected output from getopt" >&2
exit 1
;;
esac
done
# Show error message (if any).
echo -n "$script error${line:+ on line $line}${status:+ with status $status}" >&2
if (( $# > 0)); then
echo ": $1" >&2
shift
else
echo >&2
fi
# Dump code.
if [ -v line ]; then
echo "Code dump:" >&2
nl -ba "$this" | grep --color -5 "\b$line\b" >&2
fi
# At last, die in peace.
exit 1
}
# Trap errors for logging before dying.
trap 'die --line $LINENO --status $?' ERR
#---[ Utils ]---
# join , a "b c" d -> a,b c,d
# join / var local tmp -> var/local/tmp
# FOO=( a b c ); join , "${FOO[@]}" -> a,b,c
function join {
local IFS="$1"
shift
echo "$*";
}
ensure-command() {
(( 1 <= $# && $# <= 2 )) || die "Usage: ensure-command <command> [package]"
command="$1"
package="${2:-$1}"
command -v "$command" > /dev/null || \
die "Command $command not found, please install the $package package."
}
now() {
(( $# == 0 )) || die "Usage: now"
date --rfc-3339=seconds
}
mount-points() {
(( $# == 0 )) || die "Usage: mount-points"
awk '/^[[:space:]]*[^#]/ { print $2 }' /etc/fstab
}
bring-up() {
(( $# == 1 )) || die "Usage: bring-up <pool>"
local pool=$1
local mountPoint=$(realpath "$pool")
while true; do
[ -d "$pool" ] && return
if mount-points | grep -E "$mountPoint/?$" > /dev/null; then
echo "Mounting $mountPoint"
mount "$mountPoint"
[ -d "$pool" ] || die "Could not find $pool under mount point $mountPoint"
echo "$mountPoint" > "$pool/$mountfile"
return
fi
mountPoint=$(dirname "$mountPoint")
[ "$mountPoint" == "/" ] && die "Nonexistent snapshot pool $pool"
done
}
bring-down() {
(( $# == 1 )) || die "Usage: bring-down <pool>"
local pool=$1
if [ -v inProgress ] && [ -e "$inProgress" ]; then
if [ -d "$inProgress" ]; then
echo "Removing incomplete subvolume: $inProgress" >&2
btrfs subvolume delete "$inProgress" > /dev/null
elif [ -f "$inProgress" ]; then
echo "Removing incomplete subvolume serialisation: $inProgress" >&2
rm "$inProgress"
fi
fi
if [ -e "$pool/$mountfile" ]; then
mountPoint=$(<"$pool/$mountfile")
rm "$pool/$mountfile"
echo "Unmounting $mountPoint"
umount "$mountPoint"
fi
}
use-pool() {
(( $# == 1 )) || die "Usage: ensure-pool <pool>"
local pool=$1
# TODO [race condition]: the mounting and unmounting of the pool are
# outside the lock barrier.
# Mount if necessary.
bring-up "$pool"
# Open pool lockfile with fresh file descriptor. See bash(1) / REDIRECTION.
exec {fd}>"$pool/$lockfile"
# Block if pool is being used.
flock $fd
# Release lock and unmount pool when done.
trap "exec "$fd">&-; bring-down '$pool'" EXIT
}
# List subvolume names from most to least recent (irrespective of the name).
subvolume-names() {
local order=-
# Process options.
TEMP=$(getopt --longoptions ascending -o a -- "$@")
eval set -- $TEMP
while true; do
case "$1" in
--ascending|-a)
shift
order=+
;;
--)
shift
break
;;
*)
echo "Unexpected output from getopt" >&2
exit 1
;;
esac
done
(( $# == 1 )) || die "Usage: subvolume-names [--ascending|-a] <pool>"
local pool=$1
local parent=$(dirname "$pool")
btrfs subvolume list -o --sort=${order}gen "$pool" | \
while read line; do
echo ${line##*/} # keep volume name only
done
}
ensure-command flock util-linux
ensure-command btrfs btrfs-progs