-# Copyright (C) 2011 GRNET S.A.
+# Copyright (C) 2011, 2012, 2013 GRNET S.A.
# Copyright (C) 2007, 2008, 2009 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
-RESULT=/dev/ttyS1
-MONITOR=/dev/ttyS2
-
-FLOPPY_DEV=/dev/fd0
PROGNAME=$(basename $0)
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
BLOCKDEV=blockdev
REGLOOKUP=reglookup
CHNTPW=chntpw
+SGDISK=sgdisk
+GROWFS_UFS=growfs.ufs
+DUMPFS_UFS=dumpfs.ufs
+DATE="date -u" # Time in UTC
+EATMYDATA=eatmydata
+MOUNT="mount -n"
CLEANUP=( )
ERRORS=( )
WARNINGS=( )
-MSG_TYPE_ERROR="error"
-MSG_TYPE_TASK_START="task-start"
-MSG_TYPE_TASK_END="task-end"
+MSG_TYPE_TASK_START="TASK_START"
+MSG_TYPE_TASK_END="TASK_END"
+
+STDERR_LINE_SIZE=10
add_cleanup() {
local cmd=""
CLEANUP+=("$cmd")
}
-log_error() {
- ERRORS+=("$@")
- echo "ERROR: $@" | tee $RESULT >&2
- exit 1
+close_fd() {
+ local fd=$1
+
+ exec {fd}>&-
}
-warn() {
- WARNINGS+=("$@")
- echo "Warning: $@" >&2
+send_result_kvm() {
+ echo "$@" > /dev/ttyS1
}
-report_task_start() {
+send_monitor_message_kvm() {
+ echo "$@" > /dev/ttyS2
+}
- local type="$MSG_TYPE_TASK_START"
- local timestamp=$(date +%s.%N)
- local name="${PROGNAME}"
+send_result_xen() {
+ xenstore-write /local/domain/0/snf-image-helper/$DOMID "$*"
+}
+
+send_monitor_message_xen() {
+ #Broadcast the message
+ echo "$@" | socat "STDIO" "UDP-DATAGRAM:${BROADCAST}:${MONITOR_PORT},broadcast"
+}
- report+="{\"type\":\"$type\","
- report+="\"timestamp\":$(date +%s.%N),"
- report+="\"name\":\"$name\"}"
+prepare_helper() {
+ local cmdline item key val hypervisor domid
- echo "$report" > "$MONITOR"
+ read -a cmdline < /proc/cmdline
+ for item in "${cmdline[@]}"; do
+ key=$(cut -d= -f1 <<< "$item")
+ val=$(cut -d= -f2 <<< "$item")
+ if [ "$key" = "hypervisor" ]; then
+ hypervisor="$val"
+ fi
+ if [ "$key" = "rules_dev" ]; then
+ export RULES_DEV="$val"
+ fi
+ if [ "$key" = "helper_ip" ]; then
+ export IP="$val"
+ export NETWORK="$IP/24"
+ export BROADCAST="${IP%.*}.255"
+ fi
+ if [ "$key" = "monitor_port" ]; then
+ export MONITOR_PORT="$val"
+ fi
+ done
+
+ case "$hypervisor" in
+ kvm)
+ HYPERVISOR=kvm
+ ;;
+ xen-hvm|xen-pvm)
+ if [ -z "$IP" ]; then
+ echo "ERROR: \`helper_ip' not defined or empty" >&2
+ exit 1
+ fi
+ if [ -z "$MONITOR_PORT" ]; then
+ echo "ERROR: \`monitor_port' not defined or empty" >&2
+ exit 1
+ fi
+ $MOUNT -t xenfs xenfs /proc/xen
+ ip addr add "$NETWORK" dev eth0
+ ip link set eth0 up
+ ip route add default dev eth0
+ export DOMID=$(xenstore-read domid)
+ HYPERVISOR=xen
+ ;;
+ *)
+ echo "ERROR: Unknown hypervisor: \`$hypervisor'" >&2
+ exit 1
+ ;;
+ esac
+
+ export HYPERVISOR
}
-json_list() {
- declare -a items=("${!1}")
- report="["
- for item in "${items[@]}"; do
- report+="\"$(sed 's/"/\\"/g' <<< "$item")\","
- done
- if [ ${#report} -gt 1 ]; then
- # remove last comma(,)
- report="${report%?}"
+report_error() {
+ msg=""
+ if [ ${#ERRORS[*]} -eq 0 ]; then
+ # No error message. Print stderr
+ local lines stderr
+ stderr="$(tail --lines=${STDERR_LINE_SIZE} "$STDERR_FILE")"
+ lines=$(wc -l <<< "$stderr")
+ msg="STDERR:${lines}:$stderr"
+ else
+ for line in "${ERRORS[@]}"; do
+ msg+="ERROR:$line"$'\n'
+ done
fi
- report+="]"
- echo "$report"
+ send_monitor_message_${HYPERVISOR} "$msg"
}
-report_task_end() {
- local type="$MSG_TYPE_TASK_END"
- local timestam=$(date +%s.%N)
- local name=${PROGNAME}
- local warnings=$(json_list WARNINGS[@])
+log_error() {
+ ERRORS+=("$*")
- report="{\"type\":\"$type\","
- report+="\"timestamp\":$(date +%s.%N),"
- report+="\"name\":\"$name\","
- report+="\"warnings\":\"$warnings\"}"
+ send_result_${HYPERVISOR} "ERROR: $@"
- echo "$report" > "$MONITOR"
+ # Use return instead of exit. The set -x options will terminate the script
+ # but will also trigger ERR traps if defined.
+ return 1
}
-report_error() {
- local type="$MSG_TYPE_ERROR"
- local timestamp=$(date +%s.%N)
- local location="${PROGNAME}"
- local errors=$(json_list ERRORS[@])
- local warnings=$(json_list WARNINGS[@])
- local stderr="$(cat "$STDERR_FILE" | sed 's/"/\\"/g')"
+warn() {
+ echo "Warning: $@" >&2
+ send_monitor_message_${HYPERVISOR} "WARNING: $@"
+}
+
+report_task_start() {
+ send_monitor_message_${HYPERVISOR} "$MSG_TYPE_TASK_START:${PROGNAME:2}"
+}
- report="{\"type\":\"$type\","
- report+="\"timestamp\":$(date +%s),"
- report+="\"location\":\"$location\","
- report+="\"errors\":$errors,"
- report+="\"warnings\":$warnings,"
- report+="\"stderr\":\"$stderr\"}"
+report_task_end() {
+ send_monitor_message_${HYPERVISOR} "$MSG_TYPE_TASK_END:${PROGNAME:2}"
+}
- echo "$report" > "$MONITOR"
+system_poweroff() {
+ while [ 1 ]; do
+ # Credits to psomas@grnet.gr for this ...
+ echo o > /proc/sysrq-trigger
+ sleep 1
+ done
}
get_base_distro() {
echo "suse"
elif [ -e "$root_dir/etc/gentoo-release" ]; then
echo "gentoo"
+ elif [ -e "$root_dir/etc/arch-release" ]; then
+ echo "arch"
+ elif [ -e "$root_dir/etc/freebsd-update.conf" ]; then
+ echo "freebsd"
else
warn "Unknown base distro."
fi
}
get_distro() {
- local root_dir=$1
+ local root_dir distro
+ root_dir=$1
if [ -e "$root_dir/etc/debian_version" ]; then
distro="debian"
echo "suse"
elif [ -e "$root_dir/etc/gentoo-release" ]; then
echo "gentoo"
+ elif [ -e "$root_dir/etc/arch-release" ]; then
+ echo "arch"
+ elif [ -e "$root_dir/etc/freebsd-update.conf" ]; then
+ echo "freebsd"
else
warn "Unknown distro."
fi
get_partition_table() {
- local dev="$1"
+ local dev output
+ dev="$1"
# If the partition table is gpt then parted will raise an error if the
# secondary gpt is not it the end of the disk, and a warning that has to
# do with the "Last Usable LBA" entry in gpt.
if ! output="$("$PARTED" -s -m "$dev" unit s print | grep -E -v "^(Warning|Error): ")"; then
- log_error "Unable to read partition table for device \`${dev}'"
+ log_error "Unable to read partition table for device \`${dev}'. The image seems corrupted."
fi
echo "$output"
}
get_partition_table_type() {
- local ptable="$1"
+ local ptable dev field
+ ptable="$1"
- local dev="$(sed -n 2p <<< "$ptable")"
- declare -a field
+ dev="$(sed -n 2p <<< "$ptable")"
IFS=':' read -ra field <<< "$dev"
echo "${field[5]}"
local dev="$1"
local part_num="$2"
- id=$($SFDISK --print-id "$dev" "$part_num")
- if [ "$id" = "5" ]; then
+ id=$($SFDISK --force --print-id "$dev" "$part_num")
+ if [ "$id" = "5" -o "$id" = "f" ]; then
echo "yes"
else
echo "no"
}
get_extended_partition() {
- local ptable="$1"
- local dev="$(echo "$ptable" | sed -n 2p | cut -d':' -f1)"
+ local ptable dev
+ ptable="$1"
+ dev="$(echo "$ptable" | sed -n 2p | cut -d':' -f1)"
tail -n +3 <<< "$ptable" | while read line; do
part_num=$(cut -d':' -f1 <<< "$line")
}
get_logical_partitions() {
- local ptable="$1"
+ local ptable part_num
+ ptable="$1"
tail -n +3 <<< "$ptable" | while read line; do
part_num=$(cut -d':' -f1 <<< "$line")
}
get_last_primary_partition() {
- local ptable="$1"
- local dev=$(echo "ptable" | sed -n 2p | cut -d':' -f1)
+ local ptable dev output
+ ptable="$1"
+ dev=$(echo "ptable" | sed -n 2p | cut -d':' -f1)
for i in 4 3 2 1; do
if output=$(grep "^$i:" <<< "$ptable"); then
}
get_partition_to_resize() {
- local dev="$1"
+ local dev table table_type last_part last_part_num extended last_primary \
+ ext_num prim_num
+ dev="$1"
table=$(get_partition_table "$dev")
+ if [ -z "$table" ]; then
+ return 0
+ fi
if [ $(get_partition_count "$table") -eq 0 ]; then
return 0
extended=$(get_extended_partition "$table")
last_primary=$(get_last_primary_partition "$table")
ext_num=$(cut -d: -f1 <<< "$extended")
- prim_num=$(cut -d: -f1 <<< "$last_primary")
+ last_prim_num=$(cut -d: -f1 <<< "$last_primary")
if [ "$ext_num" != "$last_prim_num" ]; then
echo "$last_prim_num"
local part="$2"
local ptype="$3"
- declare -a fields
+ local fields=()
IFS=":;" read -ra fields <<< "$part"
local id="${fields[0]}"
local start="${fields[1]}"
local name="${fields[5]}"
local flags="${fields[6]//,/ }"
- $PARTED -s -m -- $device mkpart "$ptype" $fs "$start" "$end"
- for flag in $flags; do
- $PARTED -s -m $device set "$id" "$flag" on
- done
+ if [ "$ptype" = "primary" -o "$ptype" = "logical" -o "$ptype" = "extended" ]; then
+ $PARTED -s -m -- $device mkpart "$ptype" $fs "$start" "$end"
+ for flag in $flags; do
+ $PARTED -s -m $device set "$id" "$flag" on
+ done
+ else
+ # For gpt
+ start=${start:0:${#start}-1} # remove the s at the end
+ end=${end:0:${#end}-1} # remove the s at the end
+ $SGDISK -n "$id":"$start":"$end" -t "$id":"$ptype" "$device"
+ fi
}
enlarge_partition() {
- local device="$1"
- local part="$2"
- local ptype="$3"
- local new_end="$4"
+ local device part ptype new_end fields new_part table logical id
+ device="$1"
+ part="$2"
+ ptype="$3"
+ new_end="$4"
if [ -z "$new_end" ]; then
new_end=$(cut -d: -f 3 <<< "$(get_last_free_sector "$device")")
fi
- declare -a fields
+ fields=()
IFS=":;" read -ra fields <<< "$part"
fields[2]="$new_end"
- local new_part=""
+ new_part=""
for ((i = 0; i < ${#fields[*]}; i = i + 1)); do
new_part="$new_part":"${fields[$i]}"
done
# If this is an extended partition, removing it will also remove the
# logical partitions it contains. We need to save them for later.
if [ "$ptype" = "extended" ]; then
- local table="$(get_partition_table "$device")"
- local logical="$(get_logical_partitions "$table")"
+ table="$(get_partition_table "$device")"
+ logical="$(get_logical_partitions "$table")"
fi
id=${fields[0]}
}
get_last_free_sector() {
- local dev="$1"
- local unit="$2"
+ local dev unit last_line ptype
+ dev="$1"
+ unit="$2"
if [ -n "$unit" ]; then
unit="unit $unit"
fi
- local last_line="$("$PARTED" -s -m "$dev" "$unit" print free | tail -1)"
- local ptype="$(cut -d: -f 5 <<< "$last_line")"
+ last_line="$($PARTED -s -m "$dev" "$unit" print free | tail -1)"
+ ptype="$(cut -d: -f 5 <<< "$last_line")"
if [ "$ptype" = "free;" ]; then
echo "$last_line"
fi
}
+get_unattend() {
+ local target exists
+ target="$1"
+
+ # Workaround to search for $target/Unattend.xml in an case insensitive way.
+ exists=$(find "$target"/ -maxdepth 1 -iname unattend.xml)
+ if [ $(wc -l <<< "$exists") -gt 1 ]; then
+ log_error "Found multiple Unattend.xml files in the image:" $exists
+ fi
+
+ echo "$exists"
+}
+
+umount_all() {
+ local target mpoints
+ target="$1"
+
+ # Unmount file systems mounted under directory `target'
+ mpoints="$({ awk "{ if (match(\$2, \"^$target\")) { print \$2 } }" < /proc/mounts; } | sort -rbd | uniq)"
+
+ for mpoint in $mpoints; do
+ umount $mpoint
+ done
+}
+
+get_ufstype() {
+ local device ufs
+
+ device="$1"
+ ufs="$($DUMPFS_UFS "$device" | head -1 | awk '{ match ($3, /\((.+)\)/, ufs); print ufs[1] }')"
+
+ case "$ufs" in
+ UFS1)
+ echo 44bsd
+ ;;
+ UFS2)
+ echo ufs2
+ ;;
+ *)
+ log_error "Unsupported UFS type: \`$ufs' in device $device"
+ echo ""
+ ;;
+ esac
+}
+
cleanup() {
# if something fails here, it shouldn't call cleanup again...
trap - EXIT
}
task_cleanup() {
- rc=$?
+ local rc=$?
if [ $rc -eq 0 ]; then
report_task_end
}
check_if_excluded() {
- local name="$(tr [a-z] [A-Z] <<< ${PROGNAME:2})"
- local exclude="SNF_IMAGE_PROPERTY_EXCLUDE_TASK_${name}"
+ local name exclude
+ name="$(tr [a-z] [A-Z] <<< ${PROGNAME:2})"
+ exclude="SNF_IMAGE_PROPERTY_EXCLUDE_TASK_${name}"
if [ -n "${!exclude}" ]; then
- warn "Task $PROGNAME was excluded and will not run."
+ warn "Task ${PROGNAME:2} was excluded and will not run."
exit 0
fi
return 0
}
+return_success() {
+ send_result_${HYPERVISOR} "SUCCESS"
+}
+
trap cleanup EXIT
set -o pipefail