#!/bin/bash # This script must be used to manipulate xenguest images # # xenguest image topology: # params.cfg: guest global configuration file. Only edited using this script. # guest.cfg: xen main configuration file. Only edited using this script. # guest.d: directory contains files with custom xen configuration entries # which are appended to guest.cfg before starting the guest # files: directory where files used by xen configuration are stored # disk.cfg: guest disk configuration file. Only edited using this script. # (dtb, kernel image, etc) # disk: directory where files for disk creation are stored # init.[pre,d,post]: directories containing init pre, base and post scripts set -u set -e this="$0" IMAGE_TMPDIR="" usage() { cat < /dev/null 2>&1 || echo "error") if [ -n "${res}" ]; then echo "Error: File ${tstfile} is not a valid xenguest" exit 1 fi elif [ -d ${tstfile} ]; then if [ ! -f ${tstfile}/guest.cfg -o ! -f ${tstfile}/disk.cfg -o \ ! ${tstfile}/params.cfg ]; then echo "Error: Directory ${tstfile} is not a valid xenguest" exit 1 fi fi } params_config_reset() { cat < ${IMAGE_TMPDIR}/params.cfg # Xenguest-image guest global configuration # # !! This file must not be modified manually !! # # You can use xenguest-image to modify parameters. # # Guest auto boot during Dom0 init GUEST_AUTOBOOT="1" EOF } params_config_setparam() { param="${1}" shift value="$@" if [ -z "$value" ]; then sed -i "/.*${param}=.*/d" ${IMAGE_TMPDIR}/params.cfg elif grep -e "^${param}=" ${IMAGE_TMPDIR}/params.cfg > /dev/null; then sed -i "s/${param}=\".*\"/${param}=\"${value}\"/" \ ${IMAGE_TMPDIR}/params.cfg else echo "${param}=\"${value}\"" >> ${IMAGE_TMPDIR}/params.cfg fi } xen_config_reset() { cat < ${IMAGE_TMPDIR}/guest.cfg # Xenguest-image main configuraiton # # !! This file must not be modified manually !! # # You can use xenguest-image to modify parameters. # # You can add custom entries to configuration in the guest.d directory. # Guest name (set by manager when guest is created) # name = "" # Guest memory size in MB memory = 1024 # Number of VCPUS vcpus = 1 # Guest command line extra = "earlyprintk=xenboot console=hvc0 rw" # Guest root filesystem device (from guest point of view) # root = "/dev/xvda2" # Disk that will be used by the guest (set by manager when guest is created) # disk = ['phy:/dev/vg-xen/guestname,xvda,w'] EOF } get_param_file() { param="${1}" if grep ${param} ${IMAGE_TMPDIR}/guest.cfg > /dev/null 2>&1; then echo "${IMAGE_TMPDIR}/guest.cfg" else if [ ! -f ${IMAGE_TMPDIR}/guest.d/${param}.cfg ]; then mkdir -p ${IMAGE_TMPDIR}/guest.d echo "# ${param} = \"\"" > ${IMAGE_TMPDIR}/guest.d/${param}.cfg fi echo "${IMAGE_TMPDIR}/guest.d/${param}.cfg" fi } xen_config_disable_param() { param="${1}" dst=$(get_param_file ${param}) sed -i "s@.*\(${param} = .*\)\$@# \1@" ${dst} } xen_config_set_number() { param="${1}" shift value="$@" dst=$(get_param_file ${param}) sed -i "s@.*${param} = .*@${param} = ${value}@" ${dst} } xen_config_set_string() { param="${1}" shift value="$@" dst=$(get_param_file ${param}) sed -i "s@.*${param} = .*@${param} = \"${value}\"@" ${dst} } xen_config_append_string() { param="${1}" shift value="$@" dst=$(get_param_file ${param}) sed -i "s@.*${param} = \"\([^\"]*\)\"@${param} = \"\1 ${value}\"@" ${dst} } xen_config_set_list() { param="${1}" shift value=$(echo $@ | tr " " ",") dst=$(get_param_file ${param}) sed -i "s@.*${param} = .*@${param} = ['${value}']@" ${dst} } disk_config_reset() { echo "DISK_SIZE=\"0\"" > ${IMAGE_TMPDIR}/disk.cfg echo "DISK_DEVICE=\"\"" >> ${IMAGE_TMPDIR}/disk.cfg } disk_config_rm_part() { partid=$1 sed -i "/DISK_PART${partid}=.*/d" ${IMAGE_TMPDIR}/disk.cfg } disk_config_add_part() { partconf="${1}" partid=$(echo ${partconf} | sed -e "s/:.*//") partinfo=$(echo ${partconf} | sed -e "s/[^:]*://") # Make sure we don't add the same partition twice disk_config_rm_part ${partid} echo "DISK_PART${partid}=\"${partinfo}\"" >> \ ${IMAGE_TMPDIR}/disk.cfg } # We need an action as first argument action="${1:-}" if [ -z "${action}" ]; then echo "Error: No ACTION provided" usage exit 1 fi # Only help does not require a xenguest argument so treat this first # while there we also check that user is asking for a supported action case $action in help|--help|-h|-?) usage exit 0 ;; check|create|update|pack|partial) ;; dump-xenconfig|dump-diskconfig|dump-init|dump-paramsconfig) ;; extract|extract-config|extract-disk-file) ;; *) echo "Error: Invalid action $action" exit 1 ;; esac # Second argument should be the file name or directory guestfile="${2:-}" # Handle user asking for help on a specific action case $guestfile in help|--help|-h|-?) usage-${action} exit 0 ;; esac if [ -z "${guestfile}" ]; then echo "Error: no GUESTFILE provided" usage exit 1 fi shift 2 case ${action} in check) check_image ${guestfile} echo "Image is OK" exit 0 ;; dump-paramsconfig) check_image ${guestfile} echo "Guest configuration:" if [ -f ${guestfile} ]; then tar -xOf ${guestfile} ./params.cfg else cat ${guestfile}/params.cfg fi exit 0 ;; dump-xenconfig) check_image ${guestfile} echo "Xen configuration:" if [ -f ${guestfile} ]; then tar -xOf ${guestfile} ./guest.cfg tar -xOf ${guestfile} ./guest.d 2> /dev/null || true else cat ${guestfile}/guest.cfg cat ${guestfile}/guest.d/* 2> /dev/null || true fi echo exit 0 ;; dump-diskconfig) check_image ${guestfile} echo "Disk configuration:" if [ -f ${guestfile} ]; then tar -xOf ${guestfile} ./disk.cfg else cat ${guestfile}/disk.cfg fi echo exit 0 ;; dump-init) check_image ${guestfile} for init in init.d init-pre init-post; do echo "=== ${init} ===" if [ -f ${guestfile} ]; then tar -xOf ${guestfile} ./${init} 2> /dev/null || \ echo "No ${init} scripts." else cat ${guestfile}/${init}/* 2> /dev/null || \ echo "No ${init} scripts." fi echo "===============" echo done exit 0 ;; pack) check_image ${guestfile} if [ ! -d ${guestfile} ]; then echo "Error: Pack can only be done on a xenguest directory" exit 1 fi if [ -z "${1:-}" ] || [ -f ${1} ]; then echo "Error: No destination file or already existing file" exit 1 fi tar -C ${guestfile} -cf ${1} . exit 0 ;; extract) check_image ${guestfile} if [ -d ${guestfile} ]; then echo "Error: Cannot extract config from xenguest directory" exit 1 fi if [ -z "${1:-}" ] || [ ! -d ${1} ]; then echo "Error: No destination directory for image extract" exit 1 fi tar -C ${1} -xf ${guestfile} exit 0 ;; extract-config) check_image ${guestfile} if [ -d ${guestfile} ]; then echo "Error: Cannot extract config from xenguest directory" exit 1 fi if [ -z "${1:-}" ] || [ ! -d ${1} ]; then echo "Error: No destination directory for config extract" exit 1 fi #extract all but disk files tar -C ${1} --exclude='./disk' -xf ${guestfile} exit 0 ;; extract-disk-file) check_image ${guestfile} if [ -d ${guestfile} ]; then echo "Error: Cannot extract disk file from xenguest directory" >&2 exit 1 fi if [ -z "${1:-}" ]; then echo "Error: No file to extract" >&2 exit 1 fi tar -xOf ${guestfile} ./disk/${1} exit 0 ;; create) if [ -f ${guestfile} ]; then echo "Error: File ${guestfile} already exist" exit 1 elif [ -d ${guestfile} ]; then if [ -n "$(ls -A ${guestfile})" ]; then echo "Error: Directory ${guestfile} is not empty" exit 1 fi IMAGE_TMPDIR=$(realpath -m ${guestfile}) else IMAGE_TMPDIR=$(mktemp -d) fi # Create initial content params_config_reset xen_config_reset disk_config_reset ;; update) check_image ${guestfile} if [ -f ${guestfile} ]; then # Extract the image to update it IMAGE_TMPDIR=$(mktemp -d) tar -C ${IMAGE_TMPDIR} -xf ${guestfile} else IMAGE_TMPDIR=$(realpath -m ${guestfile}) fi ;; partial) if [ -e ${guestfile} -a ! -d ${guestfile} ]; then echo "Error: Invalid partial output directory" exit 1 fi mkdir -p ${guestfile} IMAGE_TMPDIR=$(realpath -m ${guestfile}) ;; *) echo "Invalid action ${action}" usage exit 1 ;; esac # Process command line arguments for arg in "${@}"; do case ${arg} in --*=*) optarg=$(echo ${arg} | sed -e "s/[^=]*=//") ;; *) optarg="" ;; esac case ${arg} in --guest-reset-config) params_config_reset ;; --set-param=*=*) param_name=$(echo $optarg | sed -e "s/=.*//") param_value=$(echo $optarg | sed -e "s/[^=]*=//") params_config_setparam "$param_name" "$param_value" ;; --set-param=*) params_config_setparam "$optarg" ;; --xen-reset-config) xen_config_create ;; --xen-name=*) if [ -z "${optarg}" ]; then xen_config_disable_param "name" else xen_config_set_string "name" "${optarg}" fi ;; --xen-kernel=*) if [ -z "${optarg}" ]; then xen_config_disable_param "kernel" rm -f ${IMAGE_TMPDIR}/files/kernel else if [ ! -f ${optarg} ]; then echo "Error: invalid kernel file ${optarg}" exit 1 fi xen_config_set_string "kernel" "files/kernel" mkdir -p ${IMAGE_TMPDIR}/files install -m 644 ${optarg} ${IMAGE_TMPDIR}/files/kernel fi ;; --xen-memory=*) xen_config_set_number "memory" ${optarg} ;; --xen-vcpus=*) xen_config_set_number "vcpus" ${optarg} ;; --xen-clean-extra) xen_config_set_string "extra" "" ;; --xen-extra=*) xen_config_append_string "extra" ${optarg} ;; --xen-root=*) if [ -z "${optarg}" ]; then xen_config_disable_param "root" else xen_config_set_string "root" "${optarg}" fi ;; --xen-device-tree=*) if [ -z "${optarg}" ]; then xen_config_disable_param "device_tree" rm -f ${IMAGE_TMPDIR}/files/guest.dtb else if [ ! -f ${optarg} ]; then echo "Error: invalid dtb file ${optarg}" exit 1 fi xen_config_set_string "device_tree" "files/guest.dtb" mkdir -p ${IMAGE_TMPDIR}/files install -m 644 ${optarg} ${IMAGE_TMPDIR}/files/guest.dtb fi ;; --xen-disk=*) if [ -z "${optarg}" ]; then xen_config_disable_param "disk" else xen_config_set_list "disk" "phy:${optarg}" "xvda" "w" fi ;; --xen-append=*) if [ ! -f ${optarg} ]; then echo "Error: invalid xen append file ${optarg}" exit 1 fi mkdir -p ${IMAGE_TMPDIR}/guest.d install -m 755 ${optarg} ${IMAGE_TMPDIR}/guest.d/. ;; --xen-add-file=*) src=$(echo "${optarg}" | sed -e "s/:.*//") dst=$(echo "${optarg}" | sed -e "s/.*://") if [ ! -f ${src} ]; then echo "Error: Invalid file: ${src}" rm -rf ${IMAGE_TMPDIR} exit 1 fi if [ -z "${dst}" ]; then dst=$(basename ${src}) fi mkdir -p ${IMAGE_TMPDIR}/files/$(dirname ${dst}) cp -f ${src} ${IMAGE_TMPDIR}/files/${dst} ;; --xen-rm-file=*) rm -f ${IMAGE_TMPDIR}/files/${optarg} ;; --init-script=*|--init-pre=*|--init-post=*) dst="" case $arg in --init-script=*) dst="init.d" ;; --init-pre=*) dst="init.pre" ;; --init-post=*) dst="init.post" ;; esac if [ ! -f ${optarg} ]; then echo "${optarg} does not point to a valid file" exit 1 else mkdir -p ${IMAGE_TMPDIR}/${dst} install -m 755 ${optarg} ${IMAGE_TMPDIR}/${dst}/. fi ;; --disk-reset-config) disk_config_reset ;; --disk-size=*) sed -i "s/DISK_SIZE=.*/DISK_SIZE=\"${optarg}\"/" \ ${IMAGE_TMPDIR}/disk.cfg ;; --disk-device=*) sed -i "s/DISK_DEVICE=.*/DISK_SIZE=\"${optarg}\"/" \ ${IMAGE_TMPDIR}/disk.cfg ;; --disk-add-part=*) disk_config_add_part ${optarg} ;; --disk-rm-part=*) disk_config_rm_part ${optarg} ;; --disk-add-file=*) src=$(echo "${optarg}" | sed -e "s/:.*//") dst=$(echo "${optarg}" | sed -e "s/.*://") if [ ! -f ${src} ]; then echo "Error: Invalid disk file: ${src}" rm -rf ${IMAGE_TMPDIR} exit 1 fi if [ -z "${dst}" ]; then dst=$(basename ${src}) fi mkdir -p ${IMAGE_TMPDIR}/disk/$(dirname ${dst}) cp -f ${src} ${IMAGE_TMPDIR}/disk/${dst} ;; --disk-rm-file=*) rm -f ${IMAGE_TMPDIR}/disk/${optarg} ;; *) echo "Unsupported command: ${arg}" exit 1 ;; esac done if [ ! -d ${guestfile} ]; then # If the original guest was in a file we need to repack the file # with the changes we did on it in the IMAGE_TMPDIR rm -f ${guestfile} tar -C ${IMAGE_TMPDIR} -cf ${guestfile} . rm -rf ${IMAGE_TMPDIR} fi