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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
|
#!/bin/bash
# mount-image-callback copied from from cloud-utils
VERBOSITY=0
TEMP_D=""
UMOUNT=""
QEMU_DISCONNECT=""
error() { echo "$@" 1>&2; }
Usage() {
cat <<EOF
Usage: ${0##*/} [ options ] file cmd [ args ]
mount a file to a temporary mount point and then
invoke the provided cmd with args
the temporary mountpoint will be put in an a environment variable
named MOUNTPOINT.
if any of the arguments are the literal string '_MOUNTPOINT_', then
they will be replaced with the mount point. Example:
${0##*/} my.img chroot _MOUNTPOINT_ /bin/sh
options:
-v | --verbose increase verbosity
--read-only use read-only mount.
-p | --proc bind mount /proc
-s | --sys bind mount /sys
-d | --dev bind mount /dev
--system-mounts bind mount /sys, /proc, /dev
--system-resolvconf copy host's resolvconf into /etc/resolvconf
EOF
}
# umount_r(mp) : unmount any filesystems under r
# this is useful to unmount a chroot that had sys, proc ... mounted
umount_r() {
local p
for p in "$@"; do
[ -n "$p" ] || continue
tac /proc/mounts | sh -c '
p=$1
while read s mp t opt a b ; do
[ "${mp}" = "${p}" -o "${mp#${p}/}" != "${mp}" ] ||
continue
umount "$mp" || exit 1
done
exit 0' umount_r "${p%/}"
[ $? -eq 0 ] || return
done
}
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; }
cleanup() {
if [ -n "$UMOUNT" ]; then
umount_r "$UMOUNT" ||
error "WARNING: unmounting filesystems failed!"
fi
if [ -n "$QEMU_DISCONNECT" ]; then
local out=""
out=$(qemu-nbd --disconnect "$QEMU_DISCONNECT" 2>&1) || {
error "warning: failed: qemu-nbd --disconnect $QEMU_DISCONNECT"
error "$out"
}
fi
[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] ||
rm --one-file-system -Rf "${TEMP_D}" ||
error "removal of temp dir failed!"
}
debug() {
local level="$1"; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
error "${@}"
}
mount_callback_umount() {
local img_in="$1" dev="" out="" mp="" ret="" img="" ro=""
local opts="" bmounts="" system_resolvconf=false
short_opts="dhpsv"
long_opts="dev,help,proc,read-only,sys,system-mounts,system-resolvconf,verbose"
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage; return 1; }
while [ $# -ne 0 ]; do
cur=${1}; next=${2};
case "$cur" in
-d|--dev) bmounts="${bmounts:+${bmounts} /dev}";;
-h|--help) Usage ; exit 0;;
-p|--proc) bmounts="${bmounts:+${bmounts} /proc}";;
-s|--sys) bmounts="${bmounts:+${bmounts} /sys}";;
--system-mounts) bmounts="/dev /proc /sys";;
--system-resolvconf) system_resolvconf=true;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--opts) opts="${opts} $next"; shift;;
--read-only) ro="ro";;
--) shift; break;;
esac
shift;
done
[ $# -ge 2 ] || { bad_Usage "must provide image and cmd"; return 1; }
[ -n "$ro" ] && $system_resolvconf && {
error "--read-only is incompatible with system-resolvconf";
return 1;
}
img_in="$1"
shift 1
img=$(readlink -f "$img_in") ||
{ error "failed to get full path to $img_in"; return 1; }
[ "$(id -u)" = "0" ] ||
{ error "sorry, must be root"; return 1; }
TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
{ error "failed to make tempdir"; return 1; }
trap cleanup EXIT
mp="${TEMP_D}/mp"
mkdir "$mp" || return
local cmd="" arg="" found=false
cmd=( )
for arg in "$@"; do
if [ "${arg}" = "_MOUNTPOINT_" ]; then
debug 1 "replaced string _MOUNTPOINT_ in arguments arg ${#cmd[@]}"
arg=$mp
fi
cmd[${#cmd[@]}]="$arg"
done
if [ "${cmd[0]##*/}" = "bash" -o "${cmd[0]##*/}" = "sh" ] &&
[ ${#cmd[@]} -eq 0 ]; then
debug 1 "invoking shell ${cmd[0]}"
error "MOUNTPOINT=$mp"
fi
local hasqemu=false
command -v "qemu-nbd" >/dev/null 2>&1 && hasqemu=true
if out=$(set -f; mount -o loop${ro:+,$ro} $opts \
"$img" "$mp" 2>&1); then
debug 1 "mounted simple filesystem image '$img_in'"
UMOUNT="$mp"
else
if ! $hasqemu; then
error "simple mount of '$img_in' failed."
error "if this not a raw image, or it is partitioned"
error "you must have qemu-nbd (apt-get install qemu-utils)"
error "mount failed with: $out"
return 1
fi
fi
if [ -z "$UMOUNT" ]; then
if [ ! -e /sys/block/nbd0 ] && ! grep -q nbd /proc/modules; then
debug 1 "trying to load nbd module"
modprobe nbd >/dev/null 2>&1
udevadm settle >/dev/null 2>&1
fi
[ -e /sys/block/nbd0 ] || {
error "no nbd kernel support, but simple mount failed"
return 1;
}
local f nbd=""
for f in /sys/block/nbd*; do
[ -d "$f" -a ! -f "$f/pid" ] && nbd=${f##*/} && break
done
if [ -z "$nbd" ]; then
error "failed to find an nbd device"
return 1;
fi
nbd="/dev/$nbd"
if ! qemu-nbd --connect "$nbd" "$img"; then
error "failed to qemu-nbd connect $img to $nbd"
return 1
fi
QEMU_DISCONNECT="$nbd"
local pfile="/sys/block/${nbd#/dev/}/pid"
if [ ! -f "$pfile" ]; then
debug 1 "waiting on pidfile for $nbd in $pfile"
local i=0
while [ ! -f "$pfile" ] && i=$(($i+1)); do
if [ $i -eq 200 ]; then
error "giving up on pidfile $pfile for $nbd"
return 1
fi
sleep .1
debug 2 "."
done
fi
debug 1 "connected $img_in to $nbd. now udev-settling"
udevadm settle >/dev/null 2>&1
local mdev="$nbd"
if [ -b "${nbd}p1" ]; then
mdev="${nbd}p1"
fi
if ( set -f; mount ${ro:+-o ${ro}} $opts "$mdev" "$mp" ) &&
UMOUNT="$mp"; then
debug 1 "mounted $mdev via qemu-nbd $nbd"
else
local pid="" pfile="/sys/block/${nbd#/dev/}/pid"
{ read pid < "$pfile" ; } >/dev/null 2>&1
[ -n "$pid" -a ! -d "/proc/$pid" ] ||
error "qemu-nbd process seems to have died. was '$pid'"
qemu-nbd --disconnect "$nbd" && QEMU_DISCONNECT=""
error "failed to mount $mdev"
return 1
fi
fi
local bindmp=""
for bindmp in $bmounts; do
[ -d "$mp${bindmp}" ] || mkdir "$mp${bindmp}" ||
{ error "failed mkdir $bindmp in mount"; return 1; }
mount --bind "$bindmp" "$mp/${bindmp}" ||
{ error "failed bind mount '$bindmp'"; return 1; }
done
if ${system_resolvconf}; then
local rcf="$mp/etc/resolv.conf"
debug 1 "replacing /etc/resolvconf"
if [ -e "$rcf" -o -L "$rcf" ]; then
local trcf="$rcf.${0##*/}.$$"
rm -f "$trcf" &&
mv "$rcf" "$trcf" && ORIG_RESOLVCONF="$trcf" ||
{ error "failed mv $rcf"; return 1; }
fi
cp "/etc/resolv.conf" "$rcf" ||
{ error "failed copy /etc/resolv.conf"; return 1; }
fi
debug 1 "invoking: MOUNTPOINT=$mp" "${cmd[@]}"
MOUNTPOINT="$mp" "${cmd[@]}"
ret=$?
if ${system_resolvconf}; then
local rcf="$mp/etc/resolv.conf"
cmp --quiet "/etc/resolv.conf" "$rcf" >/dev/null ||
error "WARN: /etc/resolv.conf changed in image!"
rm "$rcf" &&
{ [ -z "$ORIG_RESOLVCONF" ] || mv "$ORIG_RESOLVCONF" "$rcf"; } ||
{ error "failed to restore /etc/resolv.conf"; return 1; }
fi
debug 1 "cmd returned $ret. unmounting $mp"
umount_r "$mp" || { error "failed umount $img"; return 1; }
UMOUNT=""
rmdir "$mp"
if [ -n "$QEMU_DISCONNECT" ]; then
local out=""
out=$(qemu-nbd --disconnect "$QEMU_DISCONNECT" 2>&1) &&
QEMU_DISCONNECT="" || {
error "failed to disconnect $QEMU_DISCONNECT";
error "$out"
return 1;
}
fi
return $ret
}
mount_callback_umount "$@"
# vi: ts=4 noexpandtab
|