The cirros image was rebuilt against the 3.13.0-83 kernel, drivers e1000e, igbvf...
[packages/trusty/cirros-testvm.git] / cirros-testvm / src-cirros / src / sbin / growpart
diff --git a/cirros-testvm/src-cirros/src/sbin/growpart b/cirros-testvm/src-cirros/src/sbin/growpart
new file mode 100755 (executable)
index 0000000..4190585
--- /dev/null
@@ -0,0 +1,778 @@
+#!/bin/sh
+#    Copyright (C) 2011 Canonical Ltd.
+#    Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
+#
+#    Authors: Scott Moser <smoser@canonical.com>
+#             Juerg Haefliger <juerg.haefliger@hp.com>
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, version 3 of the License.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# the fudge factor. if within this many bytes dont bother
+FUDGE=${GROWPART_FUDGE:-$((1024*1024))}
+TEMP_D=""
+RESTORE_FUNC=""
+RESTORE_HUMAN=""
+VERBOSITY=0
+DISK=""
+PART=""
+PT_UPDATE=false
+DRY_RUN=0
+
+SFDISK_VERSION=""
+SFDISK_2_26="22600"
+MBR_BACKUP=""
+GPT_BACKUP=""
+_capture=""
+
+error() {
+       echo "$@" 1>&2
+}
+
+fail() {
+       [ $# -eq 0 ] || echo "FAILED:" "$@"
+       exit 2
+}
+
+nochange() {
+       echo "NOCHANGE:" "$@"
+       exit 1
+}
+
+changed() {
+       echo "CHANGED:" "$@"
+       exit 0
+}
+
+change() {
+       echo "CHANGE:" "$@"
+       exit 0
+}
+
+cleanup() {
+       if [ -n "${RESTORE_FUNC}" ]; then
+               error "***** WARNING: Resize failed, attempting to revert ******"
+               if ${RESTORE_FUNC} ; then
+                       error "***** Appears to have gone OK ****"
+               else
+                       error "***** FAILED! ******"
+                       if [ -n "${RESTORE_HUMAN}" -a -f "${RESTORE_HUMAN}" ]; then
+                               error "**** original table looked like: ****"
+                               cat "${RESTORE_HUMAN}" 1>&2
+                       else
+                               error "We seem to have not saved the partition table!"
+                       fi
+               fi
+       fi
+       [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
+}
+
+debug() {
+       local level=${1}
+       shift
+       [ "${level}" -gt "${VERBOSITY}" ] && return
+       if [ "${DEBUG_LOG}" ]; then
+               echo "$@" >>"${DEBUG_LOG}"
+       else
+               error "$@"
+       fi
+}
+
+debugcat() {
+       local level="$1"
+       shift;
+       [ "${level}" -gt "$VERBOSITY" ] && return
+       if [ "${DEBUG_LOG}" ]; then
+               cat "$@" >>"${DEBUG_LOG}"
+       else
+               cat "$@" 1>&2
+       fi
+}
+
+mktemp_d() {
+       # just a mktemp -d that doens't need mktemp if its not there.
+       _RET=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX" 2>/dev/null) &&
+               return
+       _RET=$(umask 077 && t="${TMPDIR:-/tmp}/${0##*/}.$$" &&
+               mkdir "${t}" && echo "${t}")
+       return
+}
+
+Usage() {
+       cat <<EOF
+${0##*/} disk partition
+   rewrite partition table so that partition takes up all the space it can
+   options:
+    -h | --help       print Usage and exit
+         --fudge F    if part could be resized, but change would be
+                      less than 'F' bytes, do not resize (default: ${FUDGE})
+    -N | --dry-run    only report what would be done, show new 'sfdisk -d'
+    -v | --verbose    increase verbosity / debug
+    -u | --update  R  update the the kernel partition table info after growing
+                      this requires kernel support and 'partx --update'
+                      R is one of:
+                       - 'auto'  : [default] update partition if possible
+                       - 'force' : try despite sanity checks (fail on failure)
+                       - 'off'   : do not attempt
+                       - 'on'    : fail if sanity checks indicate no support
+
+   Example:
+    - ${0##*/} /dev/sda 1
+      Resize partition 1 on /dev/sda
+EOF
+}
+
+bad_Usage() {
+       Usage 1>&2
+       error "$@"
+       exit 2
+}
+
+sfdisk_restore_legacy() {
+       sfdisk --no-reread "${DISK}" -I "${MBR_BACKUP}"
+}
+
+sfdisk_restore() {
+       # files are named: sfdisk-<device>-<offset>.bak
+       local f="" offset="" fails=0
+       for f in "${MBR_BACKUP}"*.bak; do
+               [ -f "$f" ] || continue
+               offset=${f##*-}
+               offset=${offset%.bak}
+               [ "$offset" = "$f" ] && {
+                       error "WARN: confused by file $f";
+                       continue;
+               }
+               dd "if=$f" "of=${DISK}" seek=$(($offset)) bs=1 conv=notrunc ||
+                       { error "WARN: failed restore from $f"; fails=$(($fails+1)); }
+       done
+       return $fails
+}
+
+sfdisk_worked_but_blkrrpart_failed() {
+       local ret="$1" output="$2"
+       # exit code found was just 1, but dont insist on that
+       #[ $ret -eq 1 ] || return 1
+       # Successfully wrote the new partition table
+       grep -qi "Success.* wrote.* new.* partition" "$output" &&
+               grep -qi "BLKRRPART: Device or resource busy" "$output"
+       return
+}
+
+get_sfdisk_version() {
+       # set SFDISK_VERSION to MAJOR*10000+MINOR*100+MICRO
+       local out oifs="$IFS" ver=""
+       [ -n "$SFDISK_VERSION" ] && return 0
+       # expected output: sfdisk from util-linux 2.25.2
+       out=$(sfdisk --version) ||
+               { error "failed to get sfdisk version"; return 1; }
+       set -- $out
+       ver=$4
+       case "$ver" in
+               [0-9]*.[0-9]*.[0-9]|[0-9].[0-9]*)
+                       IFS="."; set -- $ver; IFS="$oifs"
+                       SFDISK_VERSION=$(($1*10000+$2*100+${3:-0}))
+                       return 0;;
+               *) error "unexpected output in sfdisk --version [$out]"
+                       return 1;;
+       esac
+}
+
+resize_sfdisk() {
+       local humanpt="${TEMP_D}/recovery"
+       local mbr_backup="${TEMP_D}/orig.save"
+       local restore_func=""
+       local format="$1"
+
+       local change_out=${TEMP_D}/change.out
+       local dump_out=${TEMP_D}/dump.out
+       local new_out=${TEMP_D}/new.out
+       local dump_mod=${TEMP_D}/dump.mod
+       local tmp="${TEMP_D}/tmp.out"
+       local err="${TEMP_D}/err.out"
+       local mbr_max_512="4294967296"
+
+       local pt_start pt_size pt_end max_end new_size change_info dpart
+       local sector_num sector_size disk_size tot out
+
+       rqe sfd_list sfdisk --list --unit=S "$DISK" >"$tmp" ||
+               fail "failed: sfdisk --list $DISK"
+       if [ "${SFDISK_VERSION}" -lt ${SFDISK_2_26} ]; then
+               # exected output contains: Units: sectors of 512 bytes, ...
+               out=$(awk '$1 == "Units:" && $5 ~ /bytes/ { print $4 }' "$tmp") ||
+                       fail "failed to read sfdisk output"
+               if [ -z "$out" ]; then
+                       error "WARN: sector size not found in sfdisk output, assuming 512"
+                       sector_size=512
+               else
+                       sector_size="$out"
+               fi
+               local _w _cyl _w1 _heads _w2 sectors _w3 t s
+               # show-size is in units of 1024 bytes (same as /proc/partitions)
+               t=$(sfdisk --show-size "${DISK}") ||
+                       fail "failed: sfdisk --show-size $DISK"
+               disk_size=$((t*1024))
+               sector_num=$(($disk_size/$sector_size))
+               msg="disk size '$disk_size' not evenly div by sector size '$sector_size'"
+               [ "$((${disk_size}%${sector_size}))" -eq 0 ] ||
+                       error "WARN: $msg"
+               restore_func=sfdisk_restore_legacy
+       else
+               # --list first line output:
+               # Disk /dev/vda: 20 GiB, 21474836480 bytes, 41943040 sectors
+               local _x
+               read _x _x _x _x disk_size _x sector_num _x  < "$tmp"
+               sector_size=$((disk_size/$sector_num))
+               restore_func=sfdisk_restore
+       fi
+
+       debug 1 "$sector_num sectors of $sector_size. total size=${disk_size} bytes"
+       [ $(($disk_size/512)) -gt $mbr_max_512 ] &&
+               debug 1 "WARN: disk is larger than 2TB. additional space will go unused."
+
+       rqe sfd_dump sfdisk --unit=S --dump "${DISK}" >"${dump_out}" ||
+               fail "failed to dump sfdisk info for ${DISK}"
+       RESTORE_HUMAN="$dump_out"
+
+       {
+               echo "## sfdisk --unit=S --dump ${DISK}"
+               cat "${dump_out}"
+       }  >"$humanpt"
+
+       [ $? -eq 0 ] || fail "failed to save sfdisk -d output"
+       RESTORE_HUMAN="$humanpt"
+
+       debugcat 1 "$humanpt"
+
+       sed -e 's/,//g; s/start=/start /; s/size=/size /' "${dump_out}" \
+               >"${dump_mod}" ||
+               fail "sed failed on dump output"
+
+       dpart="${DISK}${PART}" # disk and partition number
+       if [ -b "${DISK}p${PART}" -a "${DISK%[0-9]}" != "${DISK}" ]; then
+               # for block devices that end in a number (/dev/nbd0)
+               # the partition is "<name>p<partition_number>" (/dev/nbd0p1)
+               dpart="${DISK}p${PART}"
+       elif [ "${DISK#/dev/loop[0-9]}" != "${DISK}" ]; then
+               # for /dev/loop devices, sfdisk output will be <name>p<number>
+               # format also, even though there is not a device there.
+               dpart="${DISK}p${PART}"
+       fi
+
+       pt_start=$(awk '$1 == pt { print $4 }' "pt=${dpart}" <"${dump_mod}") &&
+               pt_size=$(awk '$1 == pt { print $6 }' "pt=${dpart}" <"${dump_mod}") &&
+               [ -n "${pt_start}" -a -n "${pt_size}" ] &&
+               pt_end=$((${pt_size}+${pt_start})) ||
+               fail "failed to get start and end for ${dpart} in ${DISK}"
+
+       # find the minimal starting location that is >= pt_end 
+       max_end=$(awk '$3 == "start" { if($4 >= pt_end && $4 < min)
+               { min = $4 } } END { printf("%s\n",min); }' \
+               min=${sector_num} pt_end=${pt_end} "${dump_mod}") &&
+               [ -n "${max_end}" ] ||
+               fail "failed to get max_end for partition ${PART}"
+
+       mbr_max_sectors=$((mbr_max_512*$((sector_size/512))))
+       if [ "$max_end" -gt "$mbr_max_sectors" ]; then
+               max_end=$mbr_max_sectors
+       fi
+
+       if [ "$format" = "gpt" ]; then
+               # sfdisk respects 'last-lba' in input, and complains about
+               # partitions that go past that.  without it, it does the right thing.
+               sed -i '/^last-lba:/d' "$dump_out" ||
+                       fail "failed to remove last-lba from output"
+       fi
+
+       local gpt_second_size="33"
+       if [ "${max_end}" -gt "$((${sector_num}-${gpt_second_size}))" ]; then
+               # if mbr allow subsequent conversion to gpt without shrinking the
+               # partition.  safety net at cost of 33 sectors, seems reasonable.
+               # if gpt, we can't write there anyway.
+               debug 1 "padding ${gpt_second_size} sectors for gpt secondary header"
+               max_end=$((${sector_num}-${gpt_second_size}))
+       fi
+
+       debug 1 "max_end=${max_end} tot=${sector_num} pt_end=${pt_end}" \
+               "pt_start=${pt_start} pt_size=${pt_size}"
+       [ $((${pt_end})) -eq ${max_end} ] &&
+               nochange "partition ${PART} is size ${pt_size}. it cannot be grown"
+       [ $((${pt_end}+(${FUDGE}/$sector_size))) -gt ${max_end} ] &&
+               nochange "partition ${PART} could only be grown by" \
+               "$((${max_end}-${pt_end})) [fudge=$((${FUDGE}/$sector_size))]"
+
+       # now, change the size for this partition in ${dump_out} to be the
+       # new size
+       new_size=$((${max_end}-${pt_start}))
+       sed "\|^\s*${dpart} |s/${pt_size},/${new_size},/" "${dump_out}" \
+               >"${new_out}" ||
+               fail "failed to change size in output"
+
+       change_info="partition=${PART} start=${pt_start} old: size=${pt_size} end=${pt_end} new: size=${new_size},end=${max_end}"
+       if [ ${DRY_RUN} -ne 0 ]; then
+               echo "CHANGE: ${change_info}"
+               {
+                       echo "# === old sfdisk -d ==="
+                       cat "${dump_out}"
+                       echo "# === new sfdisk -d ==="
+                       cat "${new_out}"
+               } 1>&2
+               exit 0
+       fi
+
+       MBR_BACKUP="${mbr_backup}"
+       LANG=C sfdisk --no-reread "${DISK}" --force \
+               -O "${mbr_backup}" <"${new_out}" >"${change_out}" 2>&1
+       ret=$?
+       [ $ret -eq 0 ] || RESTORE_FUNC="${restore_func}"
+
+       if [ $ret -eq 0 ]; then
+               :
+       elif $PT_UPDATE &&
+               sfdisk_worked_but_blkrrpart_failed "$ret" "${change_out}"; then
+               # if the command failed, but it looks like only because
+               # the device was busy and we have pt_update, then go on
+               debug 1 "sfdisk failed, but likely only because of blkrrpart"
+       else
+               error "attempt to resize ${DISK} failed. sfdisk output below:"
+               sed 's,^,| ,' "${change_out}" 1>&2
+               fail "failed to resize"
+       fi
+
+       rq pt_update pt_update "$DISK" "$PART" ||
+               fail "pt_resize failed"
+
+       RESTORE_FUNC=""
+
+       changed "${change_info}"
+
+       # dump_out looks something like:
+       ## partition table of /tmp/out.img
+       #unit: sectors
+       #
+       #/tmp/out.img1 : start=        1, size=    48194, Id=83
+       #/tmp/out.img2 : start=    48195, size=   963900, Id=83
+       #/tmp/out.img3 : start=  1012095, size=   305235, Id=82
+       #/tmp/out.img4 : start=  1317330, size=   771120, Id= 5
+       #/tmp/out.img5 : start=  1317331, size=   642599, Id=83
+       #/tmp/out.img6 : start=  1959931, size=    48194, Id=83
+       #/tmp/out.img7 : start=  2008126, size=    80324, Id=83
+}
+
+gpt_restore() {
+       sgdisk -l "${GPT_BACKUP}" "${DISK}"
+}
+
+resize_sgdisk() {
+       GPT_BACKUP="${TEMP_D}/pt.backup"
+
+       local pt_info="${TEMP_D}/pt.info"
+       local pt_pretend="${TEMP_D}/pt.pretend"
+       local pt_data="${TEMP_D}/pt.data"
+       local out="${TEMP_D}/out"
+
+       local dev="disk=${DISK} partition=${PART}"
+
+       local pt_start pt_end pt_size last pt_max code guid name new_size
+       local old new change_info sector_size
+       
+       # Dump the original partition information and details to disk. This is
+       # used in case something goes wrong and human interaction is required
+       # to revert any changes.
+       rqe sgd_info sgdisk "--info=${PART}" --print "${DISK}" >"${pt_info}" ||
+               fail "${dev}: failed to dump original sgdisk info"
+       RESTORE_HUMAN="${pt_info}"
+
+       sector_size=$(awk '$0 ~ /^Logical sector size:.*bytes/ { print $4 }' \
+               "$pt_info") && [ -n "$sector_size" ] || {
+               sector_size=512
+               error "WARN: did not find sector size, assuming 512"
+       }
+
+       debug 1 "$dev: original sgdisk info:"
+       debugcat 1 "${pt_info}"
+
+       # Pretend to move the backup GPT header to the end of the disk and dump
+       # the resulting partition information. We use this info to determine if
+       # we have to resize the partition.
+       rqe sgd_pretend sgdisk --pretend --move-second-header \
+               --print "${DISK}" >"${pt_pretend}" ||
+               fail "${dev}: failed to dump pretend sgdisk info"
+
+       debug 1 "$dev: pretend sgdisk info"
+       debugcat 1 "${pt_pretend}"
+
+       # Extract the partition data from the pretend dump
+       awk 'found { print } ; $1 == "Number" { found = 1 }' \
+               "${pt_pretend}" >"${pt_data}" ||
+               fail "${dev}: failed to parse pretend sgdisk info"
+
+       # Get the start and end sectors of the partition to be grown
+       pt_start=$(awk '$1 == '"${PART}"' { print $2 }' "${pt_data}") &&
+               [ -n "${pt_start}" ] ||
+               fail "${dev}: failed to get start sector"
+       pt_end=$(awk '$1 == '"${PART}"' { print $3 }' "${pt_data}") &&
+               [ -n "${pt_end}" ] ||
+               fail "${dev}: failed to get end sector"
+       pt_size="$((${pt_end} - ${pt_start}))"
+
+       # Get the last usable sector
+       last=$(awk '/last usable sector is/ { print $NF }' \
+               "${pt_pretend}") && [ -n "${last}" ] ||
+               fail "${dev}: failed to get last usable sector"
+
+       # Find the minimal start sector that is >= pt_end 
+       pt_max=$(awk '{ if ($2 >= pt_end && $2 < min) { min = $2 } } END \
+               { print min }' min="${last}" pt_end="${pt_end}" \
+               "${pt_data}") && [ -n "${pt_max}" ] ||
+               fail "${dev}: failed to find max end sector"
+
+       debug 1 "${dev}: pt_start=${pt_start} pt_end=${pt_end}" \
+               "pt_size=${pt_size} pt_max=${pt_max} last=${last}"
+
+       # Check if the partition can be grown
+       [ "${pt_end}" -eq "${pt_max}" ] &&
+               nochange "${dev}: size=${pt_size}, it cannot be grown"
+       [ "$((${pt_end} + ${FUDGE}/${sector_size}))" -gt "${pt_max}" ] &&
+               nochange "${dev}: could only be grown by" \
+               "$((${pt_max} - ${pt_end})) [fudge=$((${FUDGE}/$sector_size))]"
+
+       # The partition can be grown if we made it here. Get some more info
+       # about it so we can do it properly.
+       # FIXME: Do we care about the attribute flags?
+       code=$(awk '/^Partition GUID code:/ { print $4 }' "${pt_info}")
+       guid=$(awk '/^Partition unique GUID:/ { print $4 }' "${pt_info}")
+       name=$(awk '/^Partition name:/ { gsub(/'"'"'/, "") ; \
+               if (NF >= 3) print substr($0, index($0, $3)) }' "${pt_info}")
+       [ -n "${code}" -a -n "${guid}" ] ||
+               fail "${dev}: failed to parse sgdisk details"
+
+       debug 1 "${dev}: code=${code} guid=${guid} name='${name}'"
+       local wouldrun=""
+       [ "$DRY_RUN" -ne 0 ] && wouldrun="would-run"
+
+       # Calculate the new size of the partition
+       new_size=$((${pt_max} - ${pt_start}))
+       old="old: size=${pt_size},end=${pt_end}"
+       new="new: size=${new_size},end=${pt_max}"
+       change_info="${dev}: start=${pt_start} ${old} ${new}"
+       
+       # Backup the current partition table, we're about to modify it
+       rq sgd_backup $wouldrun sgdisk "--backup=${GPT_BACKUP}" "${DISK}" ||
+               fail "${dev}: failed to backup the partition table"
+
+       # Modify the partition table. We do it all in one go (the order is
+       # important!):
+       #  - move the GPT backup header to the end of the disk
+       #  - delete the partition
+       #  - recreate the partition with the new size
+       #  - set the partition code
+       #  - set the partition GUID
+       #  - set the partition name
+       rq sgdisk_mod $wouldrun sgdisk --move-second-header "--delete=${PART}" \
+               "--new=${PART}:${pt_start}:${pt_max}" \
+               "--typecode=${PART}:${code}" \
+               "--partition-guid=${PART}:${guid}" \
+               "--change-name=${PART}:${name}" "${DISK}" &&
+               rq pt_update $wouldrun pt_update "$DISK" "$PART" || {
+               RESTORE_FUNC=gpt_restore
+               fail "${dev}: failed to repartition"
+       }
+
+       # Dry run
+       [ "${DRY_RUN}" -ne 0 ] && change "${change_info}"
+
+       changed "${change_info}"
+}
+
+kver_to_num() {
+       local kver="$1" maj="" min="" mic="0"
+       kver=${kver%%-*}
+       maj=${kver%%.*}
+       min=${kver#${maj}.}
+       min=${min%%.*}
+       mic=${kver#${maj}.${min}.}
+       [ "$kver" = "$mic" ] && mic=0
+       _RET=$(($maj*1000*1000+$min*1000+$mic))
+}
+
+kver_cmp() {
+       local op="$2" n1="" n2=""
+       kver_to_num "$1"
+       n1="$_RET"
+       kver_to_num "$3"
+       n2="$_RET"
+       [ $n1 $op $n2 ]
+}
+
+rq() {
+       # runquieterror(label, command)
+       # gobble stderr of a command unless it errors
+       local label="$1" ret="" efile=""
+       efile="$TEMP_D/$label.err"
+       shift;
+
+       local rlabel="running"
+       [ "$1" = "would-run" ] && rlabel="would-run" && shift
+
+       local cmd="" x=""
+       for x in "$@"; do
+               [ "${x#* }" != "$x" -o "${x#* \"}" != "$x" ] && x="'$x'"
+               cmd="$cmd $x"
+       done
+       cmd=${cmd# }
+
+       debug 2 "$rlabel[$label][$_capture]" "$cmd"
+       [ "$rlabel" = "would-run" ] && return 0
+
+       if [ "${_capture}" = "erronly" ]; then
+               "$@" 2>"$TEMP_D/$label.err"
+               ret=$?
+       else
+               "$@" >"$TEMP_D/$label.err" 2>&1
+               ret=$?
+       fi
+       if [ $ret -ne 0 ]; then
+               error "failed [$label:$ret]" "$@"
+               cat "$efile" 1>&2
+       fi
+       return $ret
+}
+
+rqe() {
+       local _capture="erronly"
+       rq "$@"
+}
+
+verify_ptupdate() {
+       local input="$1" found="" reason="" kver=""
+
+       # we can always satisfy 'off'
+       if [ "$input" = "off" ]; then
+               _RET="false";
+               return 0;
+       fi
+
+       if command -v partx >/dev/null 2>&1; then
+               local out="" ret=0
+               out=$(partx --help 2>&1)
+               ret=$?
+               if [ $ret -eq 0 ]; then
+                       echo "$out" | grep -q -- --update || {
+                               reason="partx has no '--update' flag in usage."
+                               found="off"
+                       }
+               else
+                       reason="'partx --help' returned $ret. assuming it is old."
+                       found="off"
+               fi
+       else
+               reason="no 'partx' command"
+               found="off"
+       fi
+
+       if [ -z "$found" ]; then
+               if [ "$(uname)" != "Linux" ]; then
+                       reason="Kernel is not Linux per uname."
+                       found="off"
+               fi
+       fi
+
+       if [ -z "$found" ]; then
+               kver=$(uname -r) || debug 1 "uname -r failed!"
+
+               if ! kver_cmp "${kver-0.0.0}" -ge 3.8.0; then
+                       reason="Kernel '$kver' < 3.8.0."
+                       found="off"
+               fi
+       fi
+
+       if [ -z "$found" ]; then
+               _RET="true"
+               return 0
+       fi
+
+       case "$input" in
+               on) error "$reason"; return 1;;
+               auto)
+                       _RET="false";
+                       debug 1 "partition update disabled: $reason"
+                       return 0;;
+               force)
+                       _RET="true"
+                       error "WARNING: ptupdate forced on even though: $reason"
+                       return 0;;
+       esac
+       error "unknown input '$input'";
+       return 1;
+}
+
+pt_update() {
+       local dev="$1" part="$2" update="${3:-$PT_UPDATE}"
+       if ! $update; then
+               return 0
+       fi
+       # partx only works on block devices (do not run on file)
+       [ -b "$dev" ] || return 0
+       partx --update "$part" "$dev"
+}
+
+has_cmd() {
+       command -v "${1}" >/dev/null 2>&1
+}
+
+resize_sgdisk_gpt() {
+       resize_sgdisk gpt
+}
+
+resize_sgdisk_dos() {
+       fail "unable to resize dos label with sgdisk"
+}
+
+resize_sfdisk_gpt() {
+       resize_sfdisk gpt
+}
+
+resize_sfdisk_dos() {
+       resize_sfdisk dos
+}
+
+get_table_format() {
+       local out="" disk="$1"
+       if has_cmd blkid && out=$(blkid -o value -s PTTYPE "$disk") &&
+               [ "$out" = "dos" -o "$out" = "gpt" ]; then
+               _RET="$out"
+               return
+       fi
+       _RET="dos"
+       if [ ${SFDISK_VERSION} -lt ${SFDISK_2_26} ] &&
+               out=$(sfdisk --id --force "$disk" 1); then
+               if [ "$out" = "ee" ]; then
+                       _RET="gpt"
+               else
+                       _RET="dos"
+               fi
+               return
+       elif out=$(LANG=C sfdisk --list "$disk"); then
+               out=$(echo "$out" | sed -e '/Disklabel type/!d' -e 's/.*: //')
+               case "$out" in
+                       gpt|dos) _RET="$out";;
+                       *) error "WARN: unknown label $out";;
+               esac
+       fi
+}
+
+get_resizer() {
+       local format="$1" user=${2:-"auto"}
+
+       case "$user" in
+               sgdisk) _RET="resize_sgdisk_$format"; return;;
+               sfdisk) _RET="resize_sfdisk_$format"; return;;
+               auto) :;;
+               *) error "unexpected input: '$user'";;
+       esac
+
+       if [ "$format" = "dos" ]; then
+               _RET="resize_sfdisk_dos"
+               return 0
+       fi
+
+       if [ "${SFDISK_VERSION}" -ge ${SFDISK_2_26} ]; then
+               _RET="resize_sfdisk_gpt"
+       elif has_cmd sgdisk; then
+               _RET="resize_sgdisk_$format"
+       else
+               error "no tools available to resize disk with '$format'"
+               return 1
+       fi
+       return 0
+}
+
+pt_update="auto"
+resizer=${GROWPART_RESIZER:-"auto"}
+while [ $# -ne 0 ]; do
+       cur=${1}
+       next=${2}
+       case "$cur" in
+               -h|--help)
+                       Usage
+                       exit 0
+                       ;;
+               --fudge)
+                       FUDGE=${next}
+                       shift
+                       ;;
+               -N|--dry-run)
+                       DRY_RUN=1
+                       ;;
+               -u|--update|--update=*)
+                       if [ "${cur#--update=}" != "$cur" ]; then
+                               next="${cur#--update=}"
+                       else
+                               shift
+                       fi
+                       case "$next" in
+                               off|auto|force|on) pt_update=$next;;
+                               *) fail "unknown --update option: $next";;
+                       esac
+                       ;;
+               -v|--verbose)
+                       VERBOSITY=$(($VERBOSITY+1))
+                       ;;
+               --)
+                       shift
+                       break
+                       ;;
+               -*)
+                       fail "unknown option ${cur}"
+                       ;;
+               *)
+                       if [ -z "${DISK}" ]; then
+                               DISK=${cur}
+                       else
+                               [ -z "${PART}" ] || fail "confused by arg ${cur}"
+                               PART=${cur}
+                       fi
+                       ;;
+       esac
+       shift
+done
+
+[ -n "${DISK}" ] || bad_Usage "must supply disk and partition-number"
+[ -n "${PART}" ] || bad_Usage "must supply partition-number"
+
+has_cmd "sfdisk" || fail "sfdisk not found"
+get_sfdisk_version || fail
+
+[ -e "${DISK}" ] || fail "${DISK}: does not exist"
+
+[ "${PART#*[!0-9]}" = "${PART}" ] || fail "partition-number must be a number"
+
+verify_ptupdate "$pt_update" || fail
+PT_UPDATE=$_RET
+
+debug 1 "update-partition set to $PT_UPDATE"
+
+mktemp_d && TEMP_D="${_RET}" || fail "failed to make temp dir"
+trap cleanup 0 # EXIT - some shells may not like 'EXIT' but are ok with 0
+
+# get the ID of the first partition to determine if it's MBR or GPT
+get_table_format "$DISK" || fail
+format=$_RET
+get_resizer "$format" "$resizer" ||
+    fail "failed to get a resizer for id '$id'"
+resizer=$_RET
+
+debug 1 "resizing $PART on $DISK using $resizer"
+"$resizer"
+
+# vi: ts=4 noexpandtab