check for killing ourself
[distro-setup] / mount-latest-subvol
1 #!/bin/bash
2 # Copyright (C) 2016 Ian Kelling
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 # usage: mount-latest-subvol
17
18 cd /
19 [[ $EUID == 0 ]] || exec sudo -E "$BASH_SOURCE" "$@"
20
21 errcatch() {
22 set -E; shopt -s extdebug
23 _err-trap() {
24 err=$?
25 exec >&2
26 set +x
27 echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]}:in \`$BASH_COMMAND' returned $err"
28 bash-trace 2
29 echo "$0: exiting with code $err"
30 exit $err
31 }
32 trap _err-trap ERR
33 set -o pipefail
34 }
35 bash-trace() {
36 local -i argc_index=0 frame i start=${1:-1} max_indent=8 indent
37 local source
38 local extdebug=false
39 if [[ $(shopt -p extdebug) == *-s* ]]; then
40 extdebug=true
41 fi
42
43 for ((frame=0; frame < ${#FUNCNAME[@]}-1; frame++)); do
44 argc=${BASH_ARGC[frame]}
45 argc_index+=$argc
46 ((frame < start)) && continue
47 if (( ${#BASH_SOURCE[@]} > 1 )); then
48 source="${BASH_SOURCE[frame+1]}:${BASH_LINENO[frame]}:"
49 fi
50 indent=$((frame-start+1))
51 indent=$((indent < max_indent ? indent : max_indent))
52 printf "%${indent}s↳%sin \`%s" '' "$source" "${FUNCNAME[frame]}"
53 if $extdebug; then
54 for ((i=argc_index-1; i >= argc_index-argc; i--)); do
55 printf " %s" "${BASH_ARGV[i]}"
56 done
57 fi
58 echo \'
59 done
60 }
61 errcatch
62
63 tu() {
64 while read -r line; do
65 file="$1"
66 grep -xFq "$line" "$file" || tee -a "$file"<<<"$line"
67 done
68 }
69 e() { printf "%s\n" "$*"; "$@"; }
70 mnt() {
71 dir=$1
72 if ! mountpoint $dir &>/dev/null; then
73 mkdir -p $dir
74 e mount $dir
75 fi
76 }
77 fstab() {
78 while read -r start mpoint end; do
79 l="$start $mpoint $end"
80 # kill off any lines that duplicate the mount point.
81 sed --follow-symlinks -ri "\%$l%b;\%^\s*\S+\s+$mpoint\s%d" /etc/fstab
82 tu /etc/fstab <<<"$l"
83 done
84 }
85 pid-check() {
86 for p in ${pids}; do
87 for m in ${my_pids[@]}; do
88 if (( p == m )); then
89 echo "$0: error: pids to kill includes our pid or a parent" >&2
90 ps -f -p $p
91 exit 1
92 fi
93 done
94 done
95 }
96 kill-dir() {
97 for sig; do
98 echo kill-dir $sig
99 found_pids=false
100 if pids=$(timeout 4 lsof -t $dir); then
101 found_pids=true
102 timeout 4 lsof -w $dir
103 pid-check
104 kill -$sig $pids
105 fi
106 # fuser will find open sockets that lsof won't, for example from gpg-agent.
107 # note: -v shows kernel processes, which then doesn't return true when we want
108 if pids=$(timeout 4 fuser -m $dir 2>/dev/null); then
109 pid-check
110 found_pids=true
111 fuser -$sig -mvk $dir
112 fi
113 sleep .5
114 if ! $found_pids; then
115 return 0
116 fi
117 done
118 return 1
119 }
120
121 force=false
122 if [[ $1 == -f ]]; then
123 force=true
124 fi
125
126 ret=0
127
128 ##### begin setup fstab for subvols we care about ######
129 first_root_crypt=$(awk '$2 == "/" {print $1}' /etc/mtab)
130 fstab <<EOF
131 $first_root_crypt /a btrfs noatime,subvol=a 0 0
132 EOF
133
134 shopt -s nullglob
135
136 # ssh and probably some other things care about parent directory
137 # ownership, and ssh doesn\'t allow any group writable parent
138 # directories, so we are forced to use a directory structure similar
139 # to home directories
140 f=(/mnt/root/btrbk/q.*)
141 if [[ -e $f ]]; then
142 fstab <<EOF
143 $first_root_crypt /q btrfs noatime,subvol=q,gid=1000 0 0
144 /q/p /p none bind 0 0
145 EOF
146 fi
147
148 f=(/mnt/root/btrbk/o.*)
149 if [[ -e $f ]]; then
150 fstab <<EOF
151 $first_root_crypt /o btrfs noatime,subvol=o 0 0
152 /o/m /m none bind 0 0
153 EOF
154 fi
155
156 if [[ $HOSTNAME == frodo ]]; then
157 fstab <<EOF
158 $first_root_crypt /i btrfs noatime,subvol=i 0 0
159 EOF
160 fi
161 ##### end setup fstab for subvols we care about ######
162
163 # get pids that this program depends on so we dont kill them
164 my_pids=($$ $PPID)
165 loop_limit=30
166 count=0
167 while [[ ${my_pids[-1]} != 1 && ${my_pids[-1]} != ${my_pids[-2]} && $count -lt $loop_limit ]]; do
168 count=$((count + 1))
169 p=$(ps -p ${my_pids[-1]} -o ppid=)
170 if [[ $p == 0 || ! $p ]]; then
171 break
172 fi
173 my_pids+=($p)
174 done
175
176
177 for vol in q a o i; do
178 d=/$vol
179 if ! awk '{print $2}' /etc/fstab | grep -xF $d &>/dev/null; then
180 continue
181 fi
182
183
184 ##### begin building up list of bind mounts ######
185 binds=() # list of bind mounts
186 roots=($d) # list of bind mounts, plus the original mount
187 while true; do
188 new_roots=()
189 for r in ${roots[@]}; do
190 # eg. when r=/q/p, for lines like
191 # /q/p /p none bind 0 0
192 # output /p
193 new_roots+=($(sed -rn "s#^$r/\S+\s+(\S+)\s+none\s+bind\s.*#\1#p" /etc/fstab))
194 done
195 (( ${#new_roots} )) || break
196 binds+=(${new_roots[@]})
197 roots=( ${new_roots[@]} )
198 done
199 ##### end building up list of bind mounts ######
200
201
202 # if latest is already mounted, make sure binds are mounted and move on
203 if e check-subvol-stale $d; then
204 mnt $d
205 for b in ${binds[@]}; do
206 mnt $b
207 done
208 continue
209 fi
210
211 fresh_snap=$(</nocow/btrfs-stale/$vol)
212 if [[ ! $fresh_snap ]]; then
213 echo "$0: error. empty fresh_snap var"
214 ret=1
215 continue
216 fi
217
218 umount_ret=true
219 unmounted=()
220 for dir in $(echo $d ${binds[*]}\ |tac -s\ ); do
221 if mountpoint $dir; then
222 if e umount -R $dir; then
223 unmounted+=($dir)
224 else
225 if ! kill-dir TERM TERM TERM INT INT HUP HUP; then
226 if $force; then kill-dir KILL; fi
227 fi
228
229 if e umount -R $dir; then
230 unmounted+=($dir)
231 else
232 echo "$0: failed to umount $dir"
233 umount_ret=false
234 ret=1
235 continue
236 fi
237 fi
238 fi
239 done
240
241 if ! $umount_ret; then
242 for dir in ${unmounted[@]}; do
243 mnt $dir
244 done
245 continue
246 fi
247
248 # todo: decipher /mnt/root, like we do in check-subvol-stale
249 cd /mnt/root
250 if [[ -e $vol ]]; then
251 e mv $vol $vol.leaf.$(date +%Y-%m-%dT%H:%M:%S%z)
252 leaf_vols=($vol.leaf.*)
253 for leaf in ${leaf_vols[@]}; do
254 leaf_secs=$(date -d ${leaf#$vol.leaf.} +%s)
255 if (( $(date +%s) - 60*60*24*60 > leaf_secs )); then # 60 days
256 e btrfs sub del $leaf
257 fi
258 done
259 fi
260 # Note, we make a few assumptions in this script, like
261 # $d was not a different subvol id than $vol, and
262 # things otherwise didn't get mounted very strangely.
263 e btrfs sub snapshot $fresh_snap $vol
264 for dir in $d ${binds[@]}; do
265 e mnt $dir
266 done
267 stale_dir=/nocow/btrfs-stale
268 rm -f $stale_dir/$d
269 done
270
271 ### disabled
272 if [[ $HOSTNAME == kdxxxxxxxxx ]]; then
273 # partitioned it with fai partitioner outside of fai,
274 # because it\'s worth it to have 1% space reserved for boot and
275 # swap partitions in case I ever want to boot off those drives.
276 # as root:
277 # . /a/bin/fai/fai-wrapper
278 # eval-fai-classfile /a/bin/fai/fai/config/class/51-multi-boot
279 # fai-setclass ROTATIONAL
280 # export LUKS_DIR=/q/root/luks/
281 # # because the partition nums existed already
282 # fai-setclass REPARTITION
283 # /a/bin/fai/fai/config/hooks/partition.DEFAULT
284
285 devs=(
286 ata-TOSHIBA_MD04ACA500_84REK6NTFS9A-part1
287 ata-TOSHIBA_MD04ACA500_84R2K773FS9A-part1
288 ata-TOSHIBA_MD04ACA500_8471K430FS9A-part1
289 ata-TOSHIBA_MD04ACA500_8481K493FS9A-part1
290 )
291 first=true
292 for dev in ${devs[@]}; do
293 if $first; then
294 first=false
295 tu /etc/fstab <<EOF
296 /dev/mapper/crypt_dev_$dev /i btrfs noatime,subvol=i,noauto 0 0
297 /dev/mapper/crypt_dev_$dev /mnt/iroot btrfs noatime,subvolid=0,noauto 0 0
298 EOF
299 fi
300 tu /etc/crypttab <<EOF
301 crypt_dev_$dev /dev/disk/by-id/$dev /q/root/luks/host-kd discard,luks
302 EOF
303 if [[ ! -e /dev/mapper/crypt_dev_$dev ]]; then
304 cryptdisks_start crypt_dev_$dev
305 fi
306 done
307 # note, could do an else here and have some kind of mount for /i
308 # on other hosts.
309 fi
310
311 exit $ret