diff options
| author | torben | 2016-12-01 10:30:54 +0100 |
|---|---|---|
| committer | torben | 2016-12-01 10:30:54 +0100 |
| commit | 09d5a988fdd0fc897c167e9d773dcc1293743955 (patch) | |
| tree | a3c92fe7d77686e53850de55ed61deea34fc12ed | |
| parent | a (diff) | |
| parent | duh 2.0 (diff) | |
| download | systemd-init-09d5a988fdd0fc897c167e9d773dcc1293743955.tar.gz systemd-init-09d5a988fdd0fc897c167e9d773dcc1293743955.tar.xz systemd-init-09d5a988fdd0fc897c167e9d773dcc1293743955.zip | |
Merge branch 'master' of git.openslx.org:openslx-ng/systemd-init
| -rwxr-xr-x | builder/bootnet-conf/module-setup.sh | 13 | ||||
| -rwxr-xr-x | builder/bootnet-conf/scripts/gen-bootnet-conf.sh | 18 | ||||
| -rwxr-xr-x | builder/build-initramfs.sh | 61 | ||||
| -rwxr-xr-x | builder/conf-tgz/hooks/fetch-config-tgz.sh | 42 | ||||
| -rwxr-xr-x | builder/conf-tgz/hooks/unpack-config-tgz.sh | 72 | ||||
| -rwxr-xr-x | builder/conf-tgz/module-setup.sh | 46 | ||||
| -rwxr-xr-x | builder/dnbd3-rootfs/hooks/fetch-config.sh | 4 | ||||
| -rwxr-xr-x | builder/dns/module-setup.sh | 13 | ||||
| -rwxr-xr-x | builder/dns/scripts/gen-resolv-conf.sh | 11 | ||||
| -rwxr-xr-x | builder/ib-conf/module-setup.sh | 13 | ||||
| -rwxr-xr-x | builder/ib-conf/scripts/gen-ib-conf.sh | 24 | ||||
| -rw-r--r-- | packager/.gitignore | 1 | ||||
| -rw-r--r-- | packager/blacklists/essential/linux-base | 2 | ||||
| -rw-r--r-- | packager/blacklists/essential/linux-extended | 4 | ||||
| -rwxr-xr-x | packager/openslx | 18 | ||||
| -rw-r--r-- | packager/openslx.config | 18 | ||||
| -rw-r--r-- | packager/openslx.functions | 640 |
17 files changed, 650 insertions, 350 deletions
diff --git a/builder/bootnet-conf/module-setup.sh b/builder/bootnet-conf/module-setup.sh new file mode 100755 index 00000000..f1d6697f --- /dev/null +++ b/builder/bootnet-conf/module-setup.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +check() { + # Tell dracut that this module should only be included if it is required + # explicitly. + return 255 +} +depends() { + echo dnbd3-rootfs +} +install() { + inst_hook pre-pivot 50 "$moddir/scripts/gen-bootnet-conf.sh" +} diff --git a/builder/bootnet-conf/scripts/gen-bootnet-conf.sh b/builder/bootnet-conf/scripts/gen-bootnet-conf.sh new file mode 100755 index 00000000..ec82f143 --- /dev/null +++ b/builder/bootnet-conf/scripts/gen-bootnet-conf.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +type emergency_shell >/dev/null 2>&1 || source /lib/dracut-lib.sh + +mac="$(getargs BOOTIF=)" +mac="$(echo $mac | cut -c 4- | tr '-' ':')" +if [ -n "${mac}" ]; then + cat << EOF >> "${NEWROOT}/etc/sysconfig/network-scripts/ifcfg-bootnet" +DEVICE=bootnet +NAME=bootnet +HWADDR=${mac} +BOOTPROTO=dhcp +ONBOOT=yes +NM_CONTROLLED=yes +EOF +else + echo "Could not parse MAC" +fi diff --git a/builder/build-initramfs.sh b/builder/build-initramfs.sh index b705fece..dcab3b1d 100755 --- a/builder/build-initramfs.sh +++ b/builder/build-initramfs.sh @@ -43,9 +43,11 @@ if ! [[ -d "$_needed_location" ]]; then ' git submodule update --remote popd - cp --recursive \ - "${_temporary_repository_location}/builder/dnbd3-rootfs" \ - "$_needed_location" + for mod in dnbd3-rootfs conf-tgz dns ib-conf bootnet-conf; do + cp --recursive \ + "${_temporary_repository_location}/builder/${mod}" \ + "$(dirname $_needed_location)/${mod}" + done rm --recursive --force "$_temporary_repository_location" fi set +o errexit @@ -203,9 +205,10 @@ parse_command_line() { debug >>> parse_command_line -p + +doc_test_capture_stderr +doc_test_contains - Error with given option "-p": +doc_test_ellipsis + Error with given option "-p": Traceback (most recent call first): ... @@ -240,9 +243,10 @@ parse_command_line() { yes >>> parse_command_line -t; echo $? + +doc_test_capture_stderr +doc_test_contains - Error with given option "-t": +doc_test_ellipsis + Error with given option "-t": Traceback (most recent call first): ... @@ -262,9 +266,10 @@ parse_command_line() { vim htop >>> parse_command_line --no-available-option; echo $? + +doc_test_capture_stderr +doc_test_contains - Error with given option "--no-available-option": +doc_test_ellipsis + Error with given option "--no-available-option": Traceback (most recent call first): ... ' @@ -547,30 +552,33 @@ main() { logging.info "Dracut isn't available yet loading it." initialize_dracut fi - - _dracut_modules_source='../../dnbd3-rootfs' - _dracut_modules_target="$(dirname "${BASH_SOURCE[0]}")/dracut/modules.d/90dnbd3-rootfs" - if [[ ! -L "$_dracut_modules_target" || "$(readlink \ - "$_dracut_modules_target")" != "$_dracut_modules_source" ]] - then - logging.info \ - "Link dnbd3 plugin into dracut modules folder ($_dracut_modules_source -> $_dracut_modules_target)." - if ! ln --symbolic --force "$_dracut_modules_source" \ - "$_dracut_modules_target" 2>/dev/null; then - logging.warn \ - "Linking \"$_dracut_modules_source\" to \"$_dracut_modules_target\" failed. We will copy them. So we have to recopy it every time to ensure that recompiled things take effect." - cp --recursive --force --no-target-directory \ - "$(dirname "${BASH_SOURCE[0]}")/$(basename "$_dracut_modules_source")" \ - "$_dracut_modules_target" + + declare -A _dracut_modules=( [dnbd3]='../../dnbd3-rootfs' [conf]='../../conf-tgz' [dns]='../../dns' [ib]='../../ib-conf' [bootnet]='../../bootnet-conf') + for mod in dnbd3 conf dns ib bootnet; do + _dracut_modules_source="${_dracut_modules[$mod]}" + _dracut_modules_target="$(dirname "${BASH_SOURCE[0]}")/dracut/modules.d/90$(basename "${_dracut_modules[$mod]}")" + if [[ ! -L "$_dracut_modules_target" || "$(readlink \ + "$_dracut_modules_target")" != "$_dracut_modules_source" ]] + then + logging.info \ + "Link ${mod} plugin into dracut modules folder ($_dracut_modules_source -> $_dracut_modules_target)." + if ! ln --symbolic --force "$_dracut_modules_source" \ + "$_dracut_modules_target" 2>/dev/null; then + logging.warn \ + "Linking \"$_dracut_modules_source\" to \"$_dracut_modules_target\" failed. We will copy them. So we have to recopy it every time to ensure that recompiled things take effect." + cp --recursive --force --no-target-directory \ + "$(dirname "${BASH_SOURCE[0]}")/$(basename "$_dracut_modules_source")" \ + "$_dracut_modules_target" + fi fi - fi + done # endregion # region prepare and perform final dracut call _loglevel='' if [ "$verbose" == 'yes' ]; then _loglevel='--verbose' fi - _modules='dnbd3-rootfs' + _modules='dnbd3-rootfs conf-tgz dns ib-conf bootnet-conf' if [ "$debug" == 'yes' ]; then _loglevel="$_loglevel --stdlog 4" _modules="$_modules i18n terminfo" @@ -598,7 +606,8 @@ main() { logging.set_commands_level debug # shellcheck disable=SC2086 "$(dirname "${BASH_SOURCE[0]}")/dracut/dracut.sh" --local \ - $_loglevel --modules "$_modules" "${dracut_parameter[@]}" \ + $_loglevel --modules "$_modules" --conf /etc/dracut.conf \ + --confdir /etc/dracut.conf.d "${dracut_parameter[@]}" \ "$file_path" _return_code=$? logging.set_commands_level "$_commands_log_level_backup" @@ -613,7 +622,9 @@ main() { exceptions.deactivate } # endregion -core.is_main && main "$@" +if core.is_main; then + main "$@" +fi # region vim modline # vim: set tabstop=4 shiftwidth=4 expandtab: # vim: foldmethod=marker foldmarker=region,endregion: diff --git a/builder/conf-tgz/hooks/fetch-config-tgz.sh b/builder/conf-tgz/hooks/fetch-config-tgz.sh new file mode 100755 index 00000000..8c3bed23 --- /dev/null +++ b/builder/conf-tgz/hooks/fetch-config-tgz.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# -*- coding: utf-8 -*- + +source '/usr/lib/rebash/core.sh' +core.import exceptions +core.import logging +type emergency_shell >/dev/null 2>&1 || source /lib/dracut-lib.sh + +exceptions.try +{ + logging.set_commands_level debug + logging.set_level debug + # NOTE: "getarg" raises an exception so deactivate exceptions for now. + exceptions.deactivate + slx_server="$(getarg slxsrv=)" + slx_server_base="$(getarg slxbase=)" + exceptions.activate + + logging.info "Download config.tgz from '${slx_server}'..." + IFS_backup="$IFS" + IFS=',' + for host in ${slx_server}; do + logging.info "Trying host \"$host\"." + if wget --timeout 5 \ + "http://${host}/${slx_server_base}/config.tgz" \ + --output-document "/etc/config.tgz" + then + break + fi + done + IFS="$IFS_backup" + + if [[ ! -e "/etc/config.tgz" ]]; then + logging.warn "Downloading 'config.tgz' from '${slx_server}' failed. Return code: $return_code" + exit 1 + fi +} +exceptions.catch +{ + logging.error "$exceptions_last_traceback" + emergency_shell "error in ${BASH_SOURCE[0]}" +} diff --git a/builder/conf-tgz/hooks/unpack-config-tgz.sh b/builder/conf-tgz/hooks/unpack-config-tgz.sh new file mode 100755 index 00000000..083fbc6e --- /dev/null +++ b/builder/conf-tgz/hooks/unpack-config-tgz.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# -*- coding: utf-8 -*- + +source '/usr/lib/rebash/core.sh' +core.import exceptions +core.import logging +type emergency_shell >/dev/null 2>&1 || source /lib/dracut-lib.sh + +# this module unpacks the config.tgz +temporary_extract_directory="$(mktemp -d)" +exceptions.try +{ + logging.set_commands_level debug + logging.set_level debug + exceptions.activate + if [[ -e "/etc/config.tgz" ]]; then + tar --extract --preserve-permissions \ + --file="/etc/config.tgz" \ + --directory="$temporary_extract_directory" + fi +} +exceptions.catch +{ + logging.error "Failed to extract '/etc/config.tgz' to '$temporary_extract_directory'." + logging.error "$exceptions_last_traceback" + emergency_shell "error in ${BASH_SOURCE[0]}" +} +# extracted to temporary directory, now check for SLX_LOCAL_CONFIGURATION +source "/etc/openslx" +exceptions.try +{ + logging.set_commands_level debug + logging.set_level debug + exceptions.activate + if [[ -z "$SLX_LOCAL_CONFIGURATION" ]]; then + logging.warn "SLX_LOCAL_CONFIGURATION is not set in '/etc/openslx'." + exit 0 + fi + if [[ ! -d "${temporary_extract_directory}/openslx-configs/${SLX_LOCAL_CONFIGURATION}" ]]; then + logging.warn "SLX_LOCAL_CONFIGURATION is set but no corresponding folder found in '/etc/config.tgz'." + exit 0 + fi + # still here? then process to merge the localized configuration files with the common files. + cd "${temporary_extract_directory}/openslx-configs/${SLX_LOCAL_CONFIGURATION}" + tar --create --preserve-permissions * | tar --extract --preserve-permissions --directory "${temporary_extract_directory}" +} +exceptions.catch +{ + # errors here are not critical, so no emergency shell + logging.error "Failed to merge local configuration files for '$SLX_LOCAL_CONFIGURATION'." + logging.error "$exceptions_last_traceback" + emergency_shell "error in ${BASH_SOURCE[0]}" +} + +# now just copy everything from the temporary_extract_directory to the future root +exceptions.try +{ + logging.set_commands_level debug + logging.set_level debug + exceptions.activate + cd "${temporary_extract_directory}" + # purge openslx-configs/ + rm -rf "openslx-configs" + tar --create --preserve-permissions * | tar --extract --preserve-permissions --directory "${NEWROOT}" +} +exceptions.catch +{ + # errors here are not critical, so no emergency shell + logging.error "Failed to copy extracted configuration files to '$NEWROOT'." + logging.error "$exceptions_last_traceback" + emergency_shell "error in ${BASH_SOURCE[0]}" +} diff --git a/builder/conf-tgz/module-setup.sh b/builder/conf-tgz/module-setup.sh new file mode 100755 index 00000000..946344bf --- /dev/null +++ b/builder/conf-tgz/module-setup.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# -*- coding: utf-8 -*- + +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd '../' && \ + cd *'dnbd3-rootfs/scripts/rebash' && pwd)/core.sh" +core.import exceptions +core.import logging + +check() { + local __doc__=' + Checks whether needed assumptions are satisfied. + + Example: + + `check` + ' + + # Here we could build our package file. + + # Tell dracut that this module should only be included if it is required + # explicitly. + return 255 +} +depends() { + local __doc__=' + Outputs all dependent dracut modules to make this module work. + + >>> depends + +doc_test_contains + base + ' + echo base +} +install() { + local __doc__=' + Copies all needed files into the initramfs image and registers all needed + dracut hooks. + + Example: + + `install` + ' + inst_hook pre-mount 10 "$moddir/hooks/fetch-config-tgz.sh" + inst_hook pre-pivot 10 "$moddir/hooks/unpack-config-tgz.sh" + inst_multiple tar wget mktemp +} diff --git a/builder/dnbd3-rootfs/hooks/fetch-config.sh b/builder/dnbd3-rootfs/hooks/fetch-config.sh index 7921a99f..23c5f004 100755 --- a/builder/dnbd3-rootfs/hooks/fetch-config.sh +++ b/builder/dnbd3-rootfs/hooks/fetch-config.sh @@ -29,7 +29,7 @@ IFS=',' for host in ${slx_server}; do logging.info "Trying host \"$host\"." if wget --timeout 5 \ - "http://${host}/${slx_server_base}${configuration_file_name}" \ + "http://${host}/${slx_server_base}/${configuration_file_name}" \ --output-document '/etc/openslx' then break @@ -38,7 +38,7 @@ done IFS="$IFS_backup" if [[ ! -e "/etc/openslx" ]]; then - logging.warn "Downloading OpenSLX configuration file from any of the servers \"${slx_server}\" at location \"${slx_server_base}${configuration_file_name}\" failed. Return code: $return_code" + logging.warn "Downloading OpenSLX configuration file from any of the servers \"${slx_server}\" at location \"${slx_server_base}/${configuration_file_name}\" failed. Return code: $return_code" exit 1 fi } diff --git a/builder/dns/module-setup.sh b/builder/dns/module-setup.sh new file mode 100755 index 00000000..3bad6548 --- /dev/null +++ b/builder/dns/module-setup.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +check() { + # Tell dracut that this module should only be included if it is required + # explicitly. + return 255 +} +depends() { + echo base +} +install() { + inst_hook pre-mount 50 "$moddir/scripts/gen-resolv-conf.sh" +} diff --git a/builder/dns/scripts/gen-resolv-conf.sh b/builder/dns/scripts/gen-resolv-conf.sh new file mode 100755 index 00000000..1601bb12 --- /dev/null +++ b/builder/dns/scripts/gen-resolv-conf.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +type emergency_shell >/dev/null 2>&1 || source /lib/dracut-lib.sh + +CONFFILE="/etc/resolv.conf" + +DNS=$(getargs dns=) +HOSTNAME=$(getargs hostname=) + +[ -n "$DNS" ] && echo "nameserver $DNS" >> "$CONFFILE" +[ -n "$HOSTNAME" ] && echo "$HOSTNAME" >> /etc/hostname diff --git a/builder/ib-conf/module-setup.sh b/builder/ib-conf/module-setup.sh new file mode 100755 index 00000000..0a80d89e --- /dev/null +++ b/builder/ib-conf/module-setup.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +check() { + # Tell dracut that this module should only be included if it is required + # explicitly. + return 255 +} +depends() { + echo dnbd3-rootfs +} +install() { + inst_hook pre-pivot 50 "$moddir/scripts/gen-ib-conf.sh" +} diff --git a/builder/ib-conf/scripts/gen-ib-conf.sh b/builder/ib-conf/scripts/gen-ib-conf.sh new file mode 100755 index 00000000..2d4b7a27 --- /dev/null +++ b/builder/ib-conf/scripts/gen-ib-conf.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +type emergency_shell >/dev/null 2>&1 || source /lib/dracut-lib.sh + +IPSUFFIX=$( + ip -4 -o addr show dev bootnet | \ + awk '{split($4,a,"/");split(a[1],b,".");print b[3]"."b[4]}' +) + +cat << EOF >> "${NEWROOT}/etc/sysconfig/network-scripts/ifcfg-ib0" +DEVICE=ib0 +NAME=ib0 +TYPE=Infiniband +BOOTPROTO=static +IPADDR=10.12.${IPSUFFIX} +BROADCAST=10.12.255.255 +NETWORK=10.12.0.0 +NETMASK=255.255.0.0 +ONBOOT=yes +NM_CONTROLLED=yes +CONNECTED_MODE=yes +MTU=65520 +EOF + diff --git a/packager/.gitignore b/packager/.gitignore new file mode 100644 index 00000000..f4ed9bc4 --- /dev/null +++ b/packager/.gitignore @@ -0,0 +1 @@ +clones diff --git a/packager/blacklists/essential/linux-base b/packager/blacklists/essential/linux-base index 54e9c294..2c3ec439 100644 --- a/packager/blacklists/essential/linux-base +++ b/packager/blacklists/essential/linux-base @@ -1,4 +1,3 @@ -- /boot/* - /cdrom/* - /dev/* - /adm/* @@ -68,6 +67,7 @@ #- /var/spool/** + /var/spool/torque/* - /var/tmp/* ++ /var/tmp/*guestfs* - /var/lib/nova/instances/* - /var/lib/libvirt diff --git a/packager/blacklists/essential/linux-extended b/packager/blacklists/essential/linux-extended index 3d81000a..90dcdca7 100644 --- a/packager/blacklists/essential/linux-extended +++ b/packager/blacklists/essential/linux-extended @@ -1,5 +1,5 @@ - /home/* - /root/* -+ /root/.bash* -+ /root/.ssh +#+ /root/.bash* +#+ /root/.ssh - /etc/cups diff --git a/packager/openslx b/packager/openslx index 9613196b..6845f9ba 100755 --- a/packager/openslx +++ b/packager/openslx @@ -31,25 +31,13 @@ else banner fi -# do we even have tools? -for TOOL in qemu-img qemu-nbd mkfs.ext4; do - which "${TOOL}" &>/dev/null || \ - perror "Could not find '${TOOL}'. Please install it." -done - # setup trap -trap cleanexit SIGINT SIGTERM +trap do_cleanup SIGINT SIGTERM SIGUSR1 EXIT # read params read_params $@ # react to given action -if [ "x$ACTION" == "xCLONE" ]; then - clone_stage4 || perror "Cloning stage4 failed with: $?" -elif [ "x$ACTION" == "xPACKAGE" ]; then - pack_qcow2 || perror "Packing as QCoW2 failed with: $?" -else - pwarning "No action given." - print_usage -fi +process_action +exit 0 diff --git a/packager/openslx.config b/packager/openslx.config index 00fc5501..985ace1a 100644 --- a/packager/openslx.config +++ b/packager/openslx.config @@ -1,8 +1,18 @@ +# default options for rsync +DEFAULT_RSYNC_OPTS="--acls \ + --hard-links \ + --xattrs \ + --archive \ + --delete \ + --delete-excluded \ + --numeric-ids \ + --verbose" + # default options used by rsync -DEFAULT_RSYNC_OPTS="-e ssh -c arcfour -oStrictHostKeyChecking=no" +DEFAULT_RSYNC_RSH="ssh -c aes256-ctr -o StrictHostKeyChecking=no" -# default size for the qcow2-container (the one containing diffs) -DEFAULT_QCOW_SIZE="10G" +# default size for the qcow2-container +DEFAULT_CONTAINER_SIZE="20G" # default filesystem type for the qcow2-container -DEFAULT_QCOW_FS="ext4" +DEFAULT_CONTAINER_FILESYSTEM="xfs" diff --git a/packager/openslx.functions b/packager/openslx.functions index 7fe74e0a..fb32a231 100644 --- a/packager/openslx.functions +++ b/packager/openslx.functions @@ -44,28 +44,37 @@ perror() { exit 1 } +# accepts one argument to be printed as a warning before dumping the help print_usage() { + [ -n "$1" ] && pwarning "$1" pinfo "USAGE:" pinfo "$ARG0 <action> <actions_params>" - pinfo "\t\tActions: '--clone', '--package'" + pinfo "\t\tActions: '--clone', '--package', '--export', 'update'" pinfo "" - pinfo "CLONING:" + pinfo "CLONING: rsync remote host to local directory" pinfo "$ARG0 --clone --host <host> [--syncdir <path>]" - pinfo "\t\tIf not specified, --syncdir = './builds/<host>/stage4'" + pinfo "\t\tIf not specified, --syncdir = './clones/<host>/stage4'" pinfo "" - pinfo "PACKAGING:" - pinfo "$ARG0 --package --syncdir <path> --container <path>" - pinfo "$ARG0 --package --host <host> --container <path>" - pinfo "\t\tIf <host> is specified, --syncdir = './builds/<host>/stage4'" - pinfo "$ARG0 --package --host <host> --syncdir <path> --container <path>" - pinfo "\t\tIf both are specified, --syncdir is used." + pinfo "PACKAGING: pack local rsync directory as qcow2-container" + pinfo "$ARG0 --package --host <host> [--container <path>] [--syncdir <dir>]" + pinfo "\t\tIf <container> is not specified, --container = './clones/<host>/stage4.qcow2'" + pinfo "\t\tIf <syncdir> is not specified, --syncdir = './clones/<host>/stage4'" + pinfo "" + pinfo "EXPORTING: rsync remote host to a new qcow2-container" + pinfo "$ARG0 --export --host <host> [--container <path>] [--syncdir <dir>]" + pinfo "\t\tIf <container> is not specified, --container = './clones/<host>/stage4.qcow2'" + pinfo "" + pinfo "UPDATING: rsync remote host to an existing qcow2-container" + pinfo "$ARG0 --update --host <host> [--container <path>]" + pinfo "\t\tIf <container> is not specified, --container = './clones/<host>/stage4.qcow2'" kill "$SELF_PID" exit 1 } -# + # helper to parse the command line arguments and fill the environment -# with the parameters given. Note that checks for validity happens -# in the respective functions, we only parse here. +# with the parameters given. Since the fallbacks for those variables +# are only dependent on the selected action, we will also post-process +# them to make sure they are set when leaving this function! read_params() { # initialize global variables declare -g FORCE=0 @@ -85,10 +94,15 @@ read_params() { --package) declare -rg ACTION="PACKAGE" ;; + --export) + declare -rg ACTION="EXPORT" + ;; + --update) + declare -rg ACTION="UPDATE" + ;; --host) if [ -z "$1" ]; then - pwarning "'--host' requires a host as parameter." - print_usage && exit 1 + print_usage "'--host' requires a host as parameter." else if [[ "$1" == --* ]]; then local _msg="A host should not start with '--'." @@ -102,8 +116,7 @@ read_params() { ;; --container) if [ -z "$1" ]; then - pwarning "'--container' requires a path as parameter." - print_usage && exit 1 + print_usage "'--container' requires a path as parameter." else if [[ "$1" == --* ]]; then local _msg="A path should not start with '--'." @@ -117,8 +130,7 @@ read_params() { ;; --syncdir) if [ -z "$1" ]; then - pwarning "'--syncdir' requires a path as parameter." - print_usage && exit 1 + print_usage "'--syncdir' requires a path as parameter." else if [[ "$1" == --* ]]; then local _msg="A path should not start with '--'." @@ -134,154 +146,101 @@ read_params() { declare -rg FORCE=1 ;; *) - pwarning "Unknown flag: $PARAM" - print_usage && exit 1 + print_usage "Unknown flag: $PARAM" ;; esac continue fi done -} - -# helper function trapped on SIGTERM/SIGINT -# Usage: do not use as is -cleanexit() { - trap '' SIGINT SIGTERM # from now on, ignore INT and TERM - pwarning "SIGINT/SIGTERM triggered - cleaning up ..." - [ -z "${_STATE}" ] && perror "'_STATE' not set, this is bad." - - case "${_STATE}" in - SYNC_DONE|QCOW_DONE) - # we are happy - pwarning "SIGINT/SIGTERM received, but everything seems fine. Check it!" - exit 0 - ;; - BLACKLISTING) - # creation of blacklists failed - # TODO do what? - ;; - SYNCING) - # error during rsync, create the .stage4 file again - [ -z "${RSYNC_TARGET}" ] && \ - perror "RSYNC_TARGET not set, this should not happen." - if [ ! -e "${RSYNC_TARGET}/.stage4" ]; then - pwarning "'.stage4' flag was lost during rsync, restoring it." - touch "${RSYNC_TARGET}/.stage4" - fi - ;; - QCOW_CREATING) - # qemu-img failed. Just remove the container if its there - if [ -n "${CONTAINER_PATH}" -a -e "${CONTAINER_PATH}" ]; then - rm -f "${CONTAINER_PATH}" || \ - pwarning "Could not remove '${CONTAINER_PATH}'." - fi - ;; - QCOW_NBD_CONNECTING) - # qemu-nbd failed - if [ -n "${NBD_DEV}" ]; then - qemu-nbd -d "${NBD_DEV}" && \ - pwarning "Could not disconnect '${NBD_DEV}'." - fi - ;; - QCOW_FSING) - # mkfs failed, disconnect and remove container - if [ -n "${NBD_DEV}" ]; then - qemu-nbd -d "${NBD_DEV}" && \ - pwarning "Could not disconnect '${NBD_DEV}'." - fi - if [ -n "${CONTAINER_PATH}" -a -e "${CONTAINER_PATH}" ]; then - rm -f "${CONTAINER_PATH}" || \ - pwarning "Could not remove '${CONTAINER_PATH}'." - fi - ;; - QCOW_MOUNTING) - # mounting failed: - # umount, disconnect and remove container and mount point - if [ -n "${NBD_MNT}" ]; then - umount "${NBD_MNT}" || pwarning "Could not umount '${NBD_MNT}'." - rmdir "${NBD_MNT}" || pwarning "Could not rmdir '${NBD_MNT}'." - fi - if [ -n "${NBD_DEV}" ]; then - qemu-nbd -d "${NBD_DEV}" && \ - perror "Could not disconnect '${NBD_DEV}'." - fi - if [ -n "${CONTAINER_PATH}" -a -e "${CONTAINER_PATH}" ]; then - rm -f "${CONTAINER_PATH}" || \ - perror "Could not remove '${CONTAINER_PATH}'." + # done parsing the arguments, exit if no action given + [ -z "$ACTION" ] && \ + print_usage "No action given" + + # now check for existance of variables + # and use fallbacks when possible if they were not specified + + # REMOTE_HOST valid? always needed! + [ -z "$REMOTE_HOST" ] && \ + perror "REMOTE_HOST not set. Use '--host'." + check_host "$REMOTE_HOST" || \ + perror "'$REMOTE_HOST' is neither an IP nor a known hostname." + # REMOTE_HOST valid - set build directory for the rest of the operations + declare -rg BUILD_DIR="${ROOT_DIR}/clones/${REMOTE_HOST}" + mkdir -p "${BUILD_DIR}" || perror "Could not create '${BUILD_DIR}'." + + # RSYNC_TARGET needs special care + if [ -z "$RSYNC_TARGET" ]; then + # none given - use fallbacks + if [ "x$ACTION" == "xCLONE" ]; then + # use default path when cloning, no need for CONTAINER_MNT + pwarning "RSYNC_TARGET not set, using: '${BUILD_DIR}/stage4/'." + declare -rg RSYNC_TARGET="${BUILD_DIR}/stage4" + else + # we always want CONTAINER_MNT here + declare -rg CONTAINER_MNT="$(mktemp -d)" + [ -z "${CONTAINER_MNT}" ] && \ + perror "Could not create temporary directory for mounting the container." + add_cleanup rmdir "${CONTAINER_MNT}" + # RSYNC_TARGET depends on the action at this point + if [ "x$ACTION" == "xPACKAGE" ]; then + # use default path when packaging + declare -rg RSYNC_TARGET="${BUILD_DIR}/stage4" + elif [ "x$ACTION" == "xUPDATE" -o "x$ACTION" == "xEXPORT" ]; then + # for action update/export, we want to sync to the mounted container + # so create a temporary directory for the mount point that we'll use later + declare -rg RSYNC_TARGET="${CONTAINER_MNT}" fi - ;; - QCOW_COPYING) - # rare case, should not happen - ;; - QCOW_CLEANING) - # should also not happen - ;; - *) - pwarning "Unknown state: ${_STATE}" - esac - # still here? then we ran into some error - exit 1 -} - - - -# helper to validate an ip -# Usage: -# valid_ip <ip> -# Returns 0 if valid, 1 otherwise. -valid_ip() { - local ip=$1 - local stat=1 + fi + fi - if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then - OIFS=$IFS - IFS='.' - ip=($ip) - IFS=$OIFS - [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \ - && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]] - stat=$? - fi - return $stat + # CONTAINER_PATH valid? + if [ -z "$CONTAINER_PATH" ]; then + # use default path: ${BUILD_DIR}/stage4.qcow2 + pwarning "CONTAINER_PATH not set. Using '${BUILD_DIR}/stage4.qcow2'." + declare -rg CONTAINER_PATH="${BUILD_DIR}/stage4.qcow2" + fi + # so from now on REMOTE_HOST, RSYNC_TARGET, CONTAINER_PATH are set (and read-only). } - -# helper to check whether a given host is valid -# Usage: -# check_host <hostname|ip> -# Returns 0 if valid, 1 otherwise -check_host() { - local HOST="$1" - [ -z "$HOST" ] && return 1 - # check if its a valid IP or a valid hostname - valid_ip "$HOST" && return 0 - host -W 2 "$HOST" && return 0 - # still here? then fail - return 1 +process_action() { + if [ "x$ACTION" == "xCLONE" ]; then + clone_host || perror "Cloning stage4 failed with: $?" + elif [ "x$ACTION" == "xPACKAGE" ]; then + pack_clone || perror "Packing as QCoW2 failed with: $?" + elif [ "x$ACTION" == "xEXPORT" ]; then + export_host || perror "Exporting failed with: $?" + elif [ "x$ACTION" == "xUPDATE" ]; then + update_container || perror "Updating failed with: $?" + else + print_usage "No action given." + fi + return 0 } -# helper to check if a dir is empty or not -# Usage: -# dir_empty <dir> -# Returns 0 if empty, 1 otherwise -is_dir_empty() { - [ $# -ne 1 ] && perror "$0 requires directory as paramter, none given." - local _dir="$1" - [ -d "$_dir" ] || return 1 - [ -n "$(ls -A $_dir)" ] && return 1 || return 0 +# wrapper to package a cloned stage4 as a qcow2 container +# - creates empty container at CONTAINER_PATH +# - mounts it to RSYNC_TARGET +# - copy RSYNC_TARGET +pack_clone() { + create_container + mount_container + copy_to_container } - -# helper to ask user for confirmation -# Usage: -# user_confirm -# Return 0 if confirmed, 1 otherwise -user_confirm() { - [ $# -ne 1 ] && perror "$0 requires the question as first argument." - pinfo "$1 [Y/n]" - local _input - read _input - [ "x${_input}" == "x" -o "x${_input}" == "xy" ] && return 0 || return 1 +# wrapper to update an existing container +# - mounts it to RSYNC_TARGET +# - clone host there +update_container() { + mount_container + clone_host +} +# wrapper to export a host directly to a container +# - create en empty qcow2 container at CONTAINER_PATH +# - mount it to RSYNC_TARGET +# - clone host there +export_host() { + create_container + mount_container + clone_host } - # ------------------------------------------------------------------------------ # # Stage4 related functions @@ -290,43 +249,21 @@ user_confirm() { # # Helper to generate a stage4 export for a remote machine per rsync. # Usage: -# clone_stage4 +# clone_host # # Note: this functions requires REMOTE_HOST and RSYNC_TARGET to be set. -clone_stage4() { - # REMOTE_HOST valid? - [ -z "$REMOTE_HOST" ] && pwarning "REMOTE_HOST not set. Use '--host'." && print_usage - if ! check_host "$REMOTE_HOST"; then - # invalid, abort - pwarning "'$REMOTE_HOST' is neither an IP nor a known hostname." - print_usage - fi - # REMOTE_HOST is valid, use it as the base for our build files - # set BUILD_DIR relative to the ROOT_DIR for the REMOTE_HOST - declare -rg BUILD_DIR="${ROOT_DIR}/builds/$REMOTE_HOST" - mkdir -p "${BUILD_DIR}" - - # RSYNC_TARGET set? - if [ -z "$RSYNC_TARGET" ]; then - pwarning "RSYNC_TARGET not set. Assuming local mode." - pinfo "Using '${BUILD_DIR}/stage4'" - declare -rg RSYNC_TARGET="${BUILD_DIR}/stage4" - fi - +clone_host() { # check if RSYNC_TARGET is valid if [ -d "${RSYNC_TARGET}" ]; then - # does it have the '.stage4' flag? - [ ! -e "${RSYNC_TARGET}/.stage4" ] && \ - perror "'${RSYNC_TARGET}' exists, but no '.stage4' flag found." \ - "Refusing to rsync there." + # does it have the '.stage4' flag? skip this check when exporting directly + [ "x$ACTION" != "xEXPORT" ] && [ ! -e "${RSYNC_TARGET}/.stage4" ] && \ + perror "'${RSYNC_TARGET}' exists, but no '.stage4' flag found. Refusing to rsync there." + #touch $RSYNC_TARGET/.stage4 && exit 0 else # not a directory, create it and set the .stage4 flag - mkdir -p "${RSYNC_TARGET}" - touch "${RSYNC_TARGET}/.stage4" + mkdir -p "${RSYNC_TARGET}" || perror "Could not create '${RSYNC_TARGET}'." fi - # mark state - _STATE='BLACKLISTING' local EXCLUDE="$BUILD_DIR/exclude-stage4" local INCLUDE="$BUILD_DIR/include-stage4" @@ -339,155 +276,149 @@ clone_stage4() { grep '^-' "$FILE" >> "$EXCLUDE" grep '^+' "$FILE" >> "$INCLUDE" done - pinfo "Done." - # prepare rsync's options - if [ -z "$DEFAULT_RSYNC_OPTS" ]; then - local RSYNC_OPTS="-e ssh -c arcfour -oStrictHostKeyChecking=no" - else - local RSYNC_OPTS="$DEFAULT_RSYNC_OPTS" - fi + # prepare rsync's general options + local RSYNC_OPTS="${DEFAULT_RSYNC_OPTS}" + [ -z "${RSYNC_OPTS}" ] && \ + RSYNC_OPTS=" + --acls \ + --hard-links \ + --xattrs \ + --archive \ + --delete \ + --delete-excluded \ + --numeric-ids + " + # prepare rsync's remote shell options + local RSYNC_RSH="$DEFAULT_RSYNC_RSH" + [ -z "${RSYNC_RSH}" ] && RSYNC_RSH="ssh -c blowfish -oStrictHostKeyChecking=no" local RSYNC_SOURCE="root@$REMOTE_HOST:/" - _STATE='SYNCING' + # if something goes wrong during rsync, we need to recreate the .stage4 flag + add_cleanup touch ${RSYNC_TARGET}/.stage4 # run rsync with the exclude/include lists created earlier cat "$INCLUDE" "$EXCLUDE" | \ - rsync --verbose \ - --archive \ - --delete \ - --delete-excluded \ - --numeric-ids \ + rsync ${RSYNC_OPTS} \ --exclude-from=- \ - "${RSYNC_OPTS}" \ + --rsh "${RSYNC_RSH}" \ "${RSYNC_SOURCE}" \ - "${RSYNC_TARGET}" \ - || perror "rsync from '${RSYNC_SOURCE}' to '${RSYNC_TARGET}' failed." - ## TODO real exit code handling - pinfo "Cloning '${REMOTE_HOST}' to '${RSYNC_TARGET}' succeeded." - _STATE='SYNC_DONE' + "${RSYNC_TARGET}" + + local -i rsync_ret=$? + if [ "x$rsync_ret" != "x0" ]; then + perror "rsync from '${RSYNC_SOURCE}' to '${RSYNC_TARGET}' failed." + fi touch "${RSYNC_TARGET}/.stage4" - return 0 + # make sure everything gets flushed + sync + pinfo "Cloning '${REMOTE_HOST}' to '${RSYNC_TARGET}' succeeded." } -# helper to build a qcow2 container from a stage4 sync directory -# Usage: -# pack_qcow2 -# -# Note that this requires CONTAINER_PATH to be set. -# RSYNC_TARGET is either the path given through the option '--syncdir' -# or the standard local path '$ROOT_DIR/builds/$REMOTE_HOST/stage4' -# is assumed to be our rsync destination directory. -pack_qcow2() { + +# Helper to create the empty container at CONTAINER_PATH +create_container() { # CONTAINER_PATH valid? - [ -z "$CONTAINER_PATH" ] && \ - pwarning "CONTAINER_PATH not set. Use '--container'." && print_usage - [ -d "$CONTAINER_PATH" ] && perror "Path to container can not be a directory!" - if [ -f "$CONTAINER_PATH" ]; then + [ -d "${CONTAINER_PATH}" ] && perror "Path to container can not be a directory!" + if [ -f "${CONTAINER_PATH}" ]; then if [ $FORCE -eq 0 ]; then perror "Container file already exists. Use '--force' to overwrite it." else # force removal - rm -f "$CONTAINER_PATH" || perror "Could not remove '$CONTAINER_PATH'" - pinfo "Removed old '$CONTAINER_PATH'." - fi - fi - - # RSYNC_TARGET valid? - if [ -z "$RSYNC_TARGET" ]; then - # if not RSYNC_TARGET was specified, we must have REMOTE_HOST - # or we do not know which stage4 we are supposed to package - [ -z "$REMOTE_HOST" ] && \ - pwarning "Need either '--syncdir' or '--host'!" && \ - print_usage - check_host "$REMOTE_HOST" || perror "Given host invalid." - pwarning "RSYNC_TARGET not set. Assuming local mode." - local RSYNC_TARGET_CANDIDATE="${ROOT_DIR}/builds/${REMOTE_HOST}/stage4" - if [ ! -d "$RSYNC_TARGET_CANDIDATE" ]; then - pwarning "Local stage4 sync not found at '${RSYNC_TARGET_CANDIDATE}'" - pwarning "Did you sync with '--syncdir' set? Then use that :)" - perror "Stage4 to package not found." + rm -f "${CONTAINER_PATH}" || perror "Could not remove '${CONTAINER_PATH}'" + pinfo "Removed old '${CONTAINER_PATH}'." fi - is_dir_empty "$RSYNC_TARGET_CANDIDATE" && \ - perror "Appears to be empty, did you clone?" - pinfo "Found '$RSYNC_TARGET_CANDIDATE', using it." - declare -rg RSYNC_TARGET="$RSYNC_TARGET_CANDIDATE" - fi - - # more sanity checks - [ ! -d "$RSYNC_TARGET" ] && perror "'$RSYNC_TARGET' not a directory!" - is_dir_empty "$RSYNC_TARGET" && \ - perror "'$RSYNC_TARGET' appears to be empty. Did you clone?" - # the ultimative check - if [ ! -e "${RSYNC_TARGET}/.stage4" ]; then - perror "No '.stage4' flag found in '${RSYNC_TARGET}'." \ - "Was this cloned properly?" fi # which size for the qcow2 container? - if [ -z "$DEFAULT_QCOW_SIZE" ]; then - local QCOW_SIZE="10G" - else - local QCOW_SIZE="$DEFAULT_QCOW_SIZE" - fi + local CONTAINER_SIZE="${DEFAULT_CONTAINER_SIZE}" + [ -z "${CONTAINER_SIZE}" ] && CONTAINER_SIZE="20G" + # so far so good - pinfo "Creating empty qcow2-container ..." - _STATE='QCOW_CREATING' - qemu-img create -f qcow2 "${CONTAINER_PATH}" "${QCOW_SIZE}" \ - || perror "qemu-img create failed with: $?" - pinfo "Done." + pinfo "Creating qcow2-container '${CONTAINER_PATH}'" + qemu-img create -f qcow2 "${CONTAINER_PATH}" "${CONTAINER_SIZE}" || \ + perror "qemu-img create failed with: $?" + # now expose it as a loop device + expose_container + # filesystem for the qcow2 container? + local CONTAINER_FILESYSTEM="${DEFAULT_CONTAINER_FILESYSTEM}" + [ -z "${CONTAINER_FILESYSTEM}" ] && CONTAINER_FILESYSTEM="xfs" + # check if we have that mkfs helper + which "mkfs.${CONTAINER_FILESYSTEM}" || \ + perror "Could not find 'mkfs.${CONTAINER_FILESYSTEM}'. Install it and retry." + + pinfo "Creating '${CONTAINER_FILESYSTEM}' filesystem on '${CONTAINER_PATH}'..." + mkfs."${CONTAINER_FILESYSTEM}" "${LOOPED_NBD_DEV}" || perror "mkfs failed with: $?" + return 0 +} +# Helper exposing the container as a loop device +expose_container() { + [ -z "${CONTAINER_PATH}" ] && \ + perror "Internal error - CONTAINER_PATH not set but should be! Check read_params()" + # find usable nbd device - pinfo "Looking for usable nbd device..." - local NBD_DEV="$(find_free_nbd)" - [ -z "${NBD_DEV}" ] && perror "Could not find usable NBD device." + declare -rg NBD_DEV="$(find_free_nbd)" + [ -n "${NBD_DEV}" ] || perror "Could not find usable NBD device." [ -b "${NBD_DEV}" ] || perror "'${NBD_DEV}' is not a block device!" - pinfo "Exporting '${CONTAINER_PATH}' using '${NBD_DEV}'..." - _STATE='QCOW_NBD_CONNECTING' + pinfo "Connecting '${CONTAINER_PATH}' to '${NBD_DEV}'" + qemu-nbd -c "${NBD_DEV}" "${CONTAINER_PATH}" || \ perror "qemu-nbd failed with: $?" - pinfo "Done." + add_cleanup disconnect_nbd + # expose as a loop device + declare -rg LOOPED_NBD_DEV="$(losetup --find)" + losetup "${LOOPED_NBD_DEV}" "${NBD_DEV}" || \ + perror "Loop device setup for '${NBD_DEV}' failed with: $?" + add_cleanup disconnect_loop + return 0 +} - # which filesystem for the qcow2 container? - if [ -z "$DEFAULT_QCOW_FS" ]; then - local QCOW_FS="ext4" - else - # check if we have mkfs helper - which "mkfs.$DEFAULT_QCOW_FS" &>/dev/null || \ - perror "Could not find 'mkfs.$DEFAULT_QCOW_FS'." - local QCOW_FS="$DEFAULT_QCOW_FS" - fi - pinfo "Creating '${QCOW_FS}' filesystem on '${CONTAINER_PATH}'..." - _STATE='QCOW_FSING' - mkfs."${QCOW_FS}" "${NBD_DEV}" || perror "mkfs failed with: $?" - pinfo "Done." +# Helper to mount CONTAINER_PATH to CONTAINER_MNT through expose_container +mount_container() { + [ -z "${CONTAINER_MNT}" ] && \ + perror "Internal error - CONTAINER_MNT not set but should be! Check read_params()" + # connect container to a loop device first, if it wasnt already done + [ -z "${LOOPED_NBD_DEV}" ] && expose_container + # lets be safe... + [ -z "${LOOPED_NBD_DEV}" ] && \ + perror "Internal error - LOOPED_NBD_DEV not set but should be! Check expose_container()" - # prepare NBD mount directory and check state to be safe - local NBD_MNT="$(mktemp -d)" - [ ! -d "${NBD_MNT}" ] && \ - perror "Making temporary dir for mounting '$NBD_DEV' failed." - is_dir_empty ${NBD_MNT} || \ - perror "'${NBD_MNT}' not empty. Refusing to mount ${NBD_DEV} to it." - - pinfo "Mounting '${NBD_DEV}' to '${NBD_MNT}'..." - _STATE='QCOW_MOUNTING' - mount "${NBD_DEV}" "${NBD_MNT}" || perror "Mount failed with: $?" - pinfo "Done." + # now we got everything, mount it + pinfo "Mounting '${LOOPED_NBD_DEV}' to '${CONTAINER_MNT}'..." + mount "${LOOPED_NBD_DEV}" "${CONTAINER_MNT}" \ + || perror "Mount failed with: $?" + add_cleanup umount_container + return 0 +} +# helper to copy the content of RSYNC_TARGET to CONTAINER_MNT +copy_to_container() { + [ -z "${RSYNC_TARGET}" ] && \ + perror "Internal error - RSYNC_TARGET not set but should be!" + [ -z "${CONTAINER_MNT}" ] && \ + perror "Internal error - CONTAINER_MNT not set but should be!" + # sanity checks + is_dir_empty "$RSYNC_TARGET" && \ + perror "'$RSYNC_TARGET' appears to be empty. Did you clone?" + # check for '.stage4' flag in the directory, indicating we cloned there + if [ ! -e "${RSYNC_TARGET}/.stage4" ]; then + perror "No '.stage4' flag found in '${RSYNC_TARGET}'." \ + "Was this cloned properly?" + fi # copy files from the stage4 directory to the mounted qcow2-container - pinfo "Copying '${RSYNC_TARGET}' to '${NBD_MNT}'..." - _STATE='QCOW_COPYING' - cp -ra "${RSYNC_TARGET}"/* "${NBD_MNT}" || perror "Copying failed with: $?" - pinfo "Done." - - pinfo "Cleaning up..." - _STATE='QCOW_CLEANING' - umount "${NBD_MNT}" || pwarning "Could not unmount '${NBD_MNT}'." - rmdir "${NBD_MNT}" || pwarning "Could not remove '${NBD_MNT}'." - qemu-nbd -d "${NBD_DEV}" || pwarning "Could not disconnect '${NBD_DEV}'." - _STATE='QCOW_DONE' - pinfo "Exporting '${RSYNC_TARGET}' to '${CONTAINER_PATH}' completed." + pinfo "Copying '${RSYNC_TARGET}' to '${CONTAINER_MNT}'..." + rsync -avAHX "${RSYNC_TARGET}"/ "${CONTAINER_MNT}"/ \ + || perror "Rsync failed with: $?" + #make sure everything is flushed + sync && return 0 } + +############################################################################### +# +# +# +############################################################################### # helper to find an unused nbd device # Usage: # find_free_nbd @@ -503,3 +434,110 @@ find_free_nbd() { echo "" } +# helper to validate an ip +# Usage: +# valid_ip <ip> +# Returns 0 if valid, 1 otherwise. +valid_ip() { + local ip=$1 + local stat=1 + + if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then + OIFS=$IFS + IFS='.' + ip=($ip) + IFS=$OIFS + [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \ + && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]] + stat=$? + fi + return $stat +} + +# helper to check whether a given host is valid +# Usage: +# check_host <hostname|ip> +# Returns 0 if valid, 1 otherwise +check_host() { + local HOST="$1" + [ -z "$HOST" ] && return 1 + # check if its a valid IP or a valid hostname + valid_ip "$HOST" && return 0 + host -W 2 "$HOST" && return 0 + # still here? then fail + return 1 +} + +# helper to check if a dir is empty or not +# Usage: +# dir_empty <dir> +# Returns 0 if empty, 1 otherwise +is_dir_empty() { + [ $# -ne 1 ] && perror "$0 requires directory as paramter, none given." + local _dir="$1" + [ -d "$_dir" ] || return 1 + [ -n "$(ls -A $_dir)" ] && return 1 || return 0 +} + +# helper to ask user for confirmation +# Usage: +# user_confirm +# Return 0 if confirmed, 1 otherwise +user_confirm() { + [ $# -ne 1 ] && perror "$0 requires the question as first argument." + pinfo "$1 [Y/n]" + local _input + read _input + [ "x${_input}" == "x" -o "x${_input}" == "xy" ] && return 0 || return 1 +} + +############################################################################### +# +# CLEANUP FUNCTIONS +# +############################################################################### +# indexed array for cleanup commands +declare -ag cleanup_commands + +# function to add a cleanup command to be executed when the program exits +add_cleanup() { + # get current command count + local -i count="${#cleanup_commands[*]}" + cleanup_commands[$count]="$*" +} + +# function trapped to EXIT, SIGINT, SIGTERM +# do the cleanup in FILO style +do_cleanup() { + trap '' SIGINT SIGTERM EXIT # from now on, ignore INT and TERM + for i in $(seq $(( ${#cleanup_commands[*]} - 1 )) -1 0); do + eval ${cleanup_commands[$i]} + done +} + +# Helper to umount + disconnect the container from all the devices +umount_container() { + [ -z "${CONTAINER_MNT}" ] && \ + perror "CONTAINER_MNT not set - is it really mounted?" + # sync? + umount -l "${CONTAINER_MNT}" || \ + perror "Failed to umount '${CONTAINER_MNT}'." +# rmdir "${CONTAINER_MNT}" || \ +# pwarning "Could not remove '${CONTAINER_MNT}'." +} + +# Helper to disconnect from loop device +disconnect_loop() { + [ -z "${LOOPED_NBD_DEV}" ] && \ + perror "Container not connected to a loop device?" + losetup -d "${LOOPED_NBD_DEV}" ||\ + perror "Could not disconnect loop device '${LOOPED_NBD_DEV}'." +} +# Helper to disconnect from nbd device +disconnect_nbd() { + [ -z "${NBD_DEV}" ] && \ + perror "Container does not seem to be connected to a NBD device?" + qemu-nbd -d "${NBD_DEV}" || \ + perror "Could not disconnect '${CONTAINER_PATH}' from '${NBD_DEV}'." +} + |
