root / snf-image-helper / common.sh @ 9ec298c7
History | View | Annotate | Download (13.4 kB)
1 |
# Copyright (C) 2011, 2012, 2013 GRNET S.A. |
---|---|
2 |
# Copyright (C) 2007, 2008, 2009 Google Inc. |
3 |
# |
4 |
# This program is free software; you can redistribute it and/or modify |
5 |
# it under the terms of the GNU General Public License as published by |
6 |
# the Free Software Foundation; either version 2 of the License, or |
7 |
# (at your option) any later version. |
8 |
# |
9 |
# This program is distributed in the hope that it will be useful, but |
10 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 |
# General Public License for more details. |
13 |
# |
14 |
# You should have received a copy of the GNU General Public License |
15 |
# along with this program; if not, write to the Free Software |
16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
17 |
# 02110-1301, USA. |
18 |
|
19 |
PROGNAME=$(basename $0) |
20 |
|
21 |
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin |
22 |
|
23 |
# Programs |
24 |
XMLSTARLET=xmlstarlet |
25 |
TUNE2FS=tune2fs |
26 |
RESIZE2FS=resize2fs |
27 |
PARTED=parted |
28 |
SFDISK=sfdisk |
29 |
MKSWAP=mkswap |
30 |
BLKID=blkid |
31 |
BLOCKDEV=blockdev |
32 |
REGLOOKUP=reglookup |
33 |
CHNTPW=chntpw |
34 |
SGDISK=sgdisk |
35 |
DATE="date -u" # Time in UTC |
36 |
EATMYDATA=eatmydata |
37 |
MOUNT="mount -n" |
38 |
|
39 |
CLEANUP=( ) |
40 |
ERRORS=( ) |
41 |
WARNINGS=( ) |
42 |
|
43 |
MSG_TYPE_TASK_START="TASK_START" |
44 |
MSG_TYPE_TASK_END="TASK_END" |
45 |
|
46 |
STDERR_LINE_SIZE=10 |
47 |
|
48 |
add_cleanup() { |
49 |
local cmd="" |
50 |
for arg; do cmd+=$(printf "%q " "$arg"); done |
51 |
CLEANUP+=("$cmd") |
52 |
} |
53 |
|
54 |
close_fd() { |
55 |
local fd=$1 |
56 |
|
57 |
exec {fd}>&- |
58 |
} |
59 |
|
60 |
send_result_kvm() { |
61 |
echo "$@" > /dev/ttyS1 |
62 |
} |
63 |
|
64 |
send_monitor_message_kvm() { |
65 |
echo "$@" > /dev/ttyS2 |
66 |
} |
67 |
|
68 |
send_result_xen() { |
69 |
xenstore-write /local/domain/0/snf-image-helper/$DOMID "$*" |
70 |
} |
71 |
|
72 |
send_monitor_message_xen() { |
73 |
#Broadcast the message |
74 |
echo "$@" | socat "STDIO" "UDP-DATAGRAM:${BROADCAST}:${MONITOR_PORT},broadcast" |
75 |
} |
76 |
|
77 |
prepare_helper() { |
78 |
local cmdline item key val hypervisor domid |
79 |
|
80 |
read -a cmdline < /proc/cmdline |
81 |
for item in "${cmdline[@]}"; do |
82 |
key=$(cut -d= -f1 <<< "$item") |
83 |
val=$(cut -d= -f2 <<< "$item") |
84 |
if [ "$key" = "hypervisor" ]; then |
85 |
hypervisor="$val" |
86 |
fi |
87 |
if [ "$key" = "rules_dev" ]; then |
88 |
export RULES_DEV="$val" |
89 |
fi |
90 |
if [ "$key" = "helper_ip" ]; then |
91 |
export IP="$val" |
92 |
export NETWORK="$IP/24" |
93 |
export BROADCAST="${IP%.*}.255" |
94 |
fi |
95 |
if [ "$key" = "monitor_port" ]; then |
96 |
export MONITOR_PORT="$val" |
97 |
fi |
98 |
done |
99 |
|
100 |
case "$hypervisor" in |
101 |
kvm) |
102 |
HYPERVISOR=kvm |
103 |
;; |
104 |
xen-hvm|xen-pvm) |
105 |
if [ -z "$IP" ]; then |
106 |
echo "ERROR: \`helper_ip' not defined or empty" >&2 |
107 |
exit 1 |
108 |
fi |
109 |
if [ -z "$MONITOR_PORT" ]; then |
110 |
echo "ERROR: \`monitor_port' not defined or empty" >&2 |
111 |
exit 1 |
112 |
fi |
113 |
$MOUNT -t xenfs xenfs /proc/xen |
114 |
ip addr add "$NETWORK" dev eth0 |
115 |
ip link set eth0 up |
116 |
ip route add default dev eth0 |
117 |
export DOMID=$(xenstore-read domid) |
118 |
HYPERVISOR=xen |
119 |
;; |
120 |
*) |
121 |
echo "ERROR: Unknown hypervisor: \`$hypervisor'" >&2 |
122 |
exit 1 |
123 |
;; |
124 |
esac |
125 |
|
126 |
export HYPERVISOR |
127 |
} |
128 |
|
129 |
report_error() { |
130 |
msg="" |
131 |
if [ ${#ERRORS[*]} -eq 0 ]; then |
132 |
# No error message. Print stderr |
133 |
local lines |
134 |
lines=$(tail --lines=${STDERR_LINE_SIZE} "$STDERR_FILE" | wc -l) |
135 |
msg="STDERR:${lines}" |
136 |
msg+=$(tail --lines=$lines "$STDERR_FILE") |
137 |
else |
138 |
for line in "${ERRORS[@]}"; do |
139 |
msg+="ERROR:$line"$'\n' |
140 |
done |
141 |
fi |
142 |
|
143 |
send_monitor_message_${HYPERVISOR} "$msg" |
144 |
} |
145 |
|
146 |
log_error() { |
147 |
ERRORS+=("$*") |
148 |
|
149 |
send_monitor_message_${HYPERVISOR} "ERROR: $@" |
150 |
send_result_${HYPERVISOR} "ERROR: $@" |
151 |
|
152 |
# Use return instead of exit. The set -x options will terminate the script |
153 |
# but will also trigger ERR traps if defined. |
154 |
return 1 |
155 |
} |
156 |
|
157 |
warn() { |
158 |
echo "Warning: $@" >&2 |
159 |
send_monitor_message_${HYPERVISOR} "WARNING: $@" |
160 |
} |
161 |
|
162 |
report_task_start() { |
163 |
send_monitor_message_${HYPERVISOR} "$MSG_TYPE_TASK_START:${PROGNAME:2}" |
164 |
} |
165 |
|
166 |
report_task_end() { |
167 |
send_monitor_message_${HYPERVISOR} "$MSG_TYPE_TASK_END:${PROGNAME:2}" |
168 |
} |
169 |
|
170 |
system_poweroff() { |
171 |
while [ 1 ]; do |
172 |
# Credits to psomas@grnet.gr for this ... |
173 |
echo o > /proc/sysrq-trigger |
174 |
sleep 1 |
175 |
done |
176 |
} |
177 |
|
178 |
get_base_distro() { |
179 |
local root_dir=$1 |
180 |
|
181 |
if [ -e "$root_dir/etc/debian_version" ]; then |
182 |
echo "debian" |
183 |
elif [ -e "$root_dir/etc/redhat-release" ]; then |
184 |
echo "redhat" |
185 |
elif [ -e "$root_dir/etc/slackware-version" ]; then |
186 |
echo "slackware" |
187 |
elif [ -e "$root_dir/etc/SuSE-release" ]; then |
188 |
echo "suse" |
189 |
elif [ -e "$root_dir/etc/gentoo-release" ]; then |
190 |
echo "gentoo" |
191 |
elif [ -e "$root_dir/etc/arch-release" ]; then |
192 |
echo "arch" |
193 |
else |
194 |
warn "Unknown base distro." |
195 |
fi |
196 |
} |
197 |
|
198 |
get_distro() { |
199 |
local root_dir distro |
200 |
root_dir=$1 |
201 |
|
202 |
if [ -e "$root_dir/etc/debian_version" ]; then |
203 |
distro="debian" |
204 |
if [ -e ${root_dir}/etc/lsb-release ]; then |
205 |
ID=$(grep ^DISTRIB_ID= ${root_dir}/etc/lsb-release | cut -d= -f2) |
206 |
if [ "x$ID" = "xUbuntu" ]; then |
207 |
distro="ubuntu" |
208 |
fi |
209 |
fi |
210 |
echo "$distro" |
211 |
elif [ -e "$root_dir/etc/fedora-release" ]; then |
212 |
echo "fedora" |
213 |
elif [ -e "$root_dir/etc/centos-release" ]; then |
214 |
echo "centos" |
215 |
elif [ -e "$root_dir/etc/redhat-release" ]; then |
216 |
echo "redhat" |
217 |
elif [ -e "$root_dir/etc/slackware-version" ]; then |
218 |
echo "slackware" |
219 |
elif [ -e "$root_dir/etc/SuSE-release" ]; then |
220 |
echo "suse" |
221 |
elif [ -e "$root_dir/etc/gentoo-release" ]; then |
222 |
echo "gentoo" |
223 |
elif [ -e "$root_dir/etc/arch-release" ]; then |
224 |
echo "arch" |
225 |
else |
226 |
warn "Unknown distro." |
227 |
fi |
228 |
} |
229 |
|
230 |
|
231 |
get_partition_table() { |
232 |
local dev output |
233 |
dev="$1" |
234 |
# If the partition table is gpt then parted will raise an error if the |
235 |
# secondary gpt is not it the end of the disk, and a warning that has to |
236 |
# do with the "Last Usable LBA" entry in gpt. |
237 |
if ! output="$("$PARTED" -s -m "$dev" unit s print | grep -E -v "^(Warning|Error): ")"; then |
238 |
log_error "Unable to read partition table for device \`${dev}'. The image seems corrupted." |
239 |
fi |
240 |
|
241 |
echo "$output" |
242 |
} |
243 |
|
244 |
get_partition_table_type() { |
245 |
local ptable dev field |
246 |
ptable="$1" |
247 |
|
248 |
dev="$(sed -n 2p <<< "$ptable")" |
249 |
IFS=':' read -ra field <<< "$dev" |
250 |
|
251 |
echo "${field[5]}" |
252 |
} |
253 |
|
254 |
get_partition_count() { |
255 |
local ptable="$1" |
256 |
|
257 |
expr $(echo "$ptable" | wc -l) - 2 |
258 |
} |
259 |
|
260 |
get_partition_by_num() { |
261 |
local ptable="$1" |
262 |
local id="$2" |
263 |
|
264 |
grep "^$id:" <<< "$ptable" |
265 |
} |
266 |
|
267 |
get_last_partition() { |
268 |
local ptable="$1" |
269 |
|
270 |
echo "$ptable" | tail -1 |
271 |
} |
272 |
|
273 |
is_extended_partition() { |
274 |
local dev="$1" |
275 |
local part_num="$2" |
276 |
|
277 |
id=$($SFDISK --print-id "$dev" "$part_num") |
278 |
if [ "$id" = "5" -o "$id" = "f" ]; then |
279 |
echo "yes" |
280 |
else |
281 |
echo "no" |
282 |
fi |
283 |
} |
284 |
|
285 |
get_extended_partition() { |
286 |
local ptable dev |
287 |
ptable="$1" |
288 |
dev="$(echo "$ptable" | sed -n 2p | cut -d':' -f1)" |
289 |
|
290 |
tail -n +3 <<< "$ptable" | while read line; do |
291 |
part_num=$(cut -d':' -f1 <<< "$line") |
292 |
if [ $(is_extended_partition "$dev" "$part_num") == "yes" ]; then |
293 |
echo "$line" |
294 |
return 0 |
295 |
fi |
296 |
done |
297 |
echo "" |
298 |
} |
299 |
|
300 |
get_logical_partitions() { |
301 |
local ptable part_num |
302 |
ptable="$1" |
303 |
|
304 |
tail -n +3 <<< "$ptable" | while read line; do |
305 |
part_num=$(cut -d':' -f1 <<< "$line") |
306 |
if [ $part_num -ge 5 ]; then |
307 |
echo "$line" |
308 |
fi |
309 |
done |
310 |
|
311 |
return 0 |
312 |
} |
313 |
|
314 |
get_last_primary_partition() { |
315 |
local ptable dev output |
316 |
ptable="$1" |
317 |
dev=$(echo "ptable" | sed -n 2p | cut -d':' -f1) |
318 |
|
319 |
for i in 4 3 2 1; do |
320 |
if output=$(grep "^$i:" <<< "$ptable"); then |
321 |
echo "$output" |
322 |
return 0 |
323 |
fi |
324 |
done |
325 |
echo "" |
326 |
} |
327 |
|
328 |
get_partition_to_resize() { |
329 |
local dev table table_type last_part last_part_num extended last_primary \ |
330 |
ext_num prim_num |
331 |
dev="$1" |
332 |
|
333 |
table=$(get_partition_table "$dev") |
334 |
|
335 |
if [ $(get_partition_count "$table") -eq 0 ]; then |
336 |
return 0 |
337 |
fi |
338 |
|
339 |
table_type=$(get_partition_table_type "$table") |
340 |
last_part=$(get_last_partition "$table") |
341 |
last_part_num=$(cut -d: -f1 <<< "$last_part") |
342 |
|
343 |
if [ "$table_type" == "msdos" -a $last_part_num -gt 4 ]; then |
344 |
extended=$(get_extended_partition "$table") |
345 |
last_primary=$(get_last_primary_partition "$table") |
346 |
ext_num=$(cut -d: -f1 <<< "$extended") |
347 |
last_prim_num=$(cut -d: -f1 <<< "$last_primary") |
348 |
|
349 |
if [ "$ext_num" != "$last_prim_num" ]; then |
350 |
echo "$last_prim_num" |
351 |
else |
352 |
echo "$last_part_num" |
353 |
fi |
354 |
else |
355 |
echo "$last_part_num" |
356 |
fi |
357 |
} |
358 |
|
359 |
create_partition() { |
360 |
local device="$1" |
361 |
local part="$2" |
362 |
local ptype="$3" |
363 |
|
364 |
local fields=() |
365 |
IFS=":;" read -ra fields <<< "$part" |
366 |
local id="${fields[0]}" |
367 |
local start="${fields[1]}" |
368 |
local end="${fields[2]}" |
369 |
local size="${fields[3]}" |
370 |
local fs="${fields[4]}" |
371 |
local name="${fields[5]}" |
372 |
local flags="${fields[6]//,/ }" |
373 |
|
374 |
if [ "$ptype" = "primary" -o "$ptype" = "logical" -o "$ptype" = "extended" ]; then |
375 |
$PARTED -s -m -- $device mkpart "$ptype" $fs "$start" "$end" |
376 |
for flag in $flags; do |
377 |
$PARTED -s -m $device set "$id" "$flag" on |
378 |
done |
379 |
else |
380 |
# For gpt |
381 |
start=${start:0:${#start}-1} # remove the s at the end |
382 |
end=${end:0:${#end}-1} # remove the s at the end |
383 |
$SGDISK -n "$id":"$start":"$end" -t "$id":"$ptype" "$device" |
384 |
fi |
385 |
} |
386 |
|
387 |
enlarge_partition() { |
388 |
local device part ptype new_end fields new_part table logical id |
389 |
device="$1" |
390 |
part="$2" |
391 |
ptype="$3" |
392 |
new_end="$4" |
393 |
|
394 |
if [ -z "$new_end" ]; then |
395 |
new_end=$(cut -d: -f 3 <<< "$(get_last_free_sector "$device")") |
396 |
fi |
397 |
|
398 |
fields=() |
399 |
IFS=":;" read -ra fields <<< "$part" |
400 |
fields[2]="$new_end" |
401 |
|
402 |
new_part="" |
403 |
for ((i = 0; i < ${#fields[*]}; i = i + 1)); do |
404 |
new_part="$new_part":"${fields[$i]}" |
405 |
done |
406 |
new_part=${new_part:1} |
407 |
|
408 |
# If this is an extended partition, removing it will also remove the |
409 |
# logical partitions it contains. We need to save them for later. |
410 |
if [ "$ptype" = "extended" ]; then |
411 |
table="$(get_partition_table "$device")" |
412 |
logical="$(get_logical_partitions "$table")" |
413 |
fi |
414 |
|
415 |
id=${fields[0]} |
416 |
$PARTED -s -m "$device" rm "$id" |
417 |
create_partition "$device" "$new_part" "$ptype" |
418 |
|
419 |
if [ "$ptype" = "extended" ]; then |
420 |
# Recreate logical partitions |
421 |
echo "$logical" | while read logical_part; do |
422 |
create_partition "$device" "$logical_part" "logical" |
423 |
done |
424 |
fi |
425 |
} |
426 |
|
427 |
get_last_free_sector() { |
428 |
local dev unit last_line ptype |
429 |
dev="$1" |
430 |
unit="$2" |
431 |
|
432 |
if [ -n "$unit" ]; then |
433 |
unit="unit $unit" |
434 |
fi |
435 |
|
436 |
last_line="$($PARTED -s -m "$dev" "$unit" print free | tail -1)" |
437 |
ptype="$(cut -d: -f 5 <<< "$last_line")" |
438 |
|
439 |
if [ "$ptype" = "free;" ]; then |
440 |
echo "$last_line" |
441 |
fi |
442 |
} |
443 |
|
444 |
get_unattend() { |
445 |
local target exists |
446 |
target="$1" |
447 |
|
448 |
# Workaround to search for $target/Unattend.xml in an case insensitive way. |
449 |
exists=$(find "$target"/ -maxdepth 1 -iname unattend.xml) |
450 |
if [ $(wc -l <<< "$exists") -gt 1 ]; then |
451 |
log_error "Found multiple Unattend.xml files in the image:" $exists |
452 |
fi |
453 |
|
454 |
echo "$exists" |
455 |
} |
456 |
|
457 |
umount_all() { |
458 |
local target mpoints |
459 |
target="$1" |
460 |
|
461 |
# Unmount file systems mounted under directory `target' |
462 |
mpoints="$({ awk "{ if (match(\$2, \"^$target\")) { print \$2 } }" < /proc/mounts; } | sort -rbd | uniq)" |
463 |
|
464 |
for mpoint in $mpoints; do |
465 |
umount $mpoint |
466 |
done |
467 |
} |
468 |
|
469 |
cleanup() { |
470 |
# if something fails here, it shouldn't call cleanup again... |
471 |
trap - EXIT |
472 |
|
473 |
if [ ${#CLEANUP[*]} -gt 0 ]; then |
474 |
LAST_ELEMENT=$((${#CLEANUP[*]}-1)) |
475 |
REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0) |
476 |
for i in $REVERSE_INDEXES; do |
477 |
# If something fails here, it's better to retry it for a few times |
478 |
# before we give up with an error. This is needed for kpartx when |
479 |
# dealing with ntfs partitions mounted through fuse. umount is not |
480 |
# synchronous and may return while the partition is still busy. A |
481 |
# premature attempt to delete partition mappings through kpartx on |
482 |
# a device that hosts previously mounted ntfs partition may fail |
483 |
# with a `device-mapper: remove ioctl failed: Device or resource |
484 |
# busy' error. A sensible workaround for this is to wait for a |
485 |
# while and then try again. |
486 |
local cmd=${CLEANUP[$i]} |
487 |
$cmd || for interval in 0.25 0.5 1 2 4; do |
488 |
echo "Command $cmd failed!" |
489 |
echo "I'll wait for $interval secs and will retry..." |
490 |
sleep $interval |
491 |
$cmd && break |
492 |
done |
493 |
if [ "$?" != "0" ]; then |
494 |
echo "Giving Up..." |
495 |
exit 1; |
496 |
fi |
497 |
done |
498 |
fi |
499 |
} |
500 |
|
501 |
task_cleanup() { |
502 |
local rc=$? |
503 |
|
504 |
if [ $rc -eq 0 ]; then |
505 |
report_task_end |
506 |
else |
507 |
report_error |
508 |
fi |
509 |
|
510 |
cleanup |
511 |
} |
512 |
|
513 |
check_if_excluded() { |
514 |
local name exclude |
515 |
name="$(tr [a-z] [A-Z] <<< ${PROGNAME:2})" |
516 |
exclude="SNF_IMAGE_PROPERTY_EXCLUDE_TASK_${name}" |
517 |
if [ -n "${!exclude}" ]; then |
518 |
warn "Task ${PROGNAME:2} was excluded and will not run." |
519 |
exit 0 |
520 |
fi |
521 |
|
522 |
return 0 |
523 |
} |
524 |
|
525 |
|
526 |
return_success() { |
527 |
send_result_${HYPERVISOR} "SUCCESS" |
528 |
} |
529 |
|
530 |
trap cleanup EXIT |
531 |
set -o pipefail |
532 |
|
533 |
STDERR_FILE=$(mktemp) |
534 |
add_cleanup rm -f "$STDERR_FILE" |
535 |
exec 2> >(tee -a "$STDERR_FILE" >&2) |
536 |
|
537 |
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai : |