#!/bin/bash # # Copyright (C) 2021 Red Hat, Inc. All rights reserved. # # This file is part of LVM2. # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions # of the GNU General Public License v.2. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # Author: Zdenek Kabelac # # Script for converting VDO volumes to lvm2 VDO LVs # # Needed utilities: # lvm, dmsetup, # vdo, vdo2lvm, # grep, awk, sed, blockdev, readlink, mkdir # # Conversion is using 'vdo convert' support from VDO manager to move # existing VDO header by 2M which makes space to place in PV header # and VG metadata area, and then create VDOPOOL LV and VDO LV in such VG. # set -euE -o pipefail TOOL=vdoimport _SAVEPATH=$PATH PATH="/sbin:/usr/sbin:/bin:/usr/sbin:$PATH" # user may override lvm location by setting LVM_BINARY LVM=${LVM_BINARY:-lvm} VDO=${VDO_BINARY:-vdo} VDOCONF=${VDOCONF:-} BLOCKDEV="blockdev" READLINK="readlink" READLINK_E="-e" MKDIR="mkdir" TEMPDIR="${TMPDIR:-/tmp}/${TOOL}_${RANDOM}$$" DM_DEV_DIR="${DM_DEV_DIR:-/dev}" DRY=0 VERB="" FORCE="" YES="" # default name for converted VG and its VDO LV NAME="vdovg/vdolvol" # help message tool_usage() { echo "${TOOL}: Utility to convert VDO volume to VDO LV." echo echo " ${TOOL} [options] " echo echo " Options:" echo " -f | --force Bypass sanity checks" echo " -h | --help Show this help message" echo " -n | --name Specifies VG/LV name for converted VDO volume" echo " -v | --verbose Be verbose" echo " -y | --yes Answer \"yes\" at any prompts" echo " --dry-run Print commands without running them" exit } verbose() { test -z "$VERB" || echo "$TOOL:" "$@" } # Support multi-line error messages error() { for i in "$@" ; do echo "$TOOL: $i" >&2 done cleanup 1 } dry() { if [ "$DRY" -ne 0 ]; then verbose "Dry execution" "$@" return 0 fi verbose "Executing" "$@" "$@" } cleanup() { trap '' 2 rm -rf "$TEMPDIR" # error exit status for break exit "${1:-1}" } get_enabled_value_() { case "$1" in enabled) echo "1" ;; *) echo "0" ;; esac } get_kb_size_with_unit_() { case "$1" in *[kK]) echo $(( ${1%[kK]} )) ;; *[mM]) echo $(( ${1%[mM]} * 1024 )) ;; *[gG]) echo $(( ${1%[gG]} * 1024 * 1024 )) ;; *[tT]) echo $(( ${1%[tT]} * 1024 * 1024 * 1024 )) ;; *[pP]) echo $(( ${1%[pP]} * 1024 * 1024 * 1024 * 1024 )) ;; esac } get_mb_size_with_unit_() { case "$1" in *[mM]) echo $(( ${1%[mM]} )) ;; *[gG]) echo $(( ${1%[gG]} * 1024 )) ;; *[tT]) echo $(( ${1%[tT]} * 1024 * 1024 )) ;; *[pP]) echo $(( ${1%[pP]} * 1024 * 1024 * 1024 )) ;; esac } # Figure out largest possible extent size usable for VG # $1 physical size # $2 logical size get_largest_extent_size_() { local max=4 local i local d for i in 8 16 32 64 128 256 512 1024 2048 4096 ; do d=$(( $1 / i )) test $(( d * i )) -eq "$1" || break d=$(( $2 / i )) test $(( d * i )) -eq "$2" || break max=$i done echo "$max" } # detect LV on the given device # dereference device name if it is symbolic link detect_lv_() { local DEVICE=$1 local MAJOR local MINOR local SYSVOLUME local MAJORMINOR DEVICE=${1/#"${DM_DEV_DIR}/"/} DEVICE=$("$READLINK" $READLINK_E "$DM_DEV_DIR/$DEVICE") test -n "$DEVICE" || error "Cannot get readlink \"$1\"." RDEVICE=$DEVICE case "$RDEVICE" in # hardcoded /dev since udev does not create these entries elsewhere /dev/dm-[0-9]*) read -r <"/sys/block/${RDEVICE#/dev/}/dm/name" SYSVOLUME 2>&1 && DEVICE="$DM_DEV_DIR/mapper/$SYSVOLUME" read -r <"/sys/block/${RDEVICE#/dev/}/dev" MAJORMINOR 2>&1 || error "Cannot get major:minor for \"$DEVICE\"." MAJOR=${MAJORMINOR%%:*} MINOR=${MAJORMINOR##*:} ;; *) STAT=$(stat --format "MAJOR=\$((0x%t)) MINOR=\$((0x%T))" "$RDEVICE") test -n "$STAT" || error "Cannot get major:minor for \"$DEVICE\"." eval "$STAT" ;; esac eval "$(dmsetup info -c -j "$MAJOR" -m "$MINOR" -o uuid,name --noheadings --nameprefixes --separator ' ')" } # parse yaml config files into 'prefix_yaml_part_names=("value")' strings parse_yaml_() { local yaml_file=$1 local prefix=$2 local s local w local fs s='[[:space:]]*' w='[a-zA-Z0-9_.-]*' fs="$(echo @|tr @ '\034')" ( sed -ne '/^--/s|--||g; s|\"|\\\"|g; s/[[:space:]]*$//g;' \ -e 's/\$/\\\$/g' \ -e "/#.*[\"\']/!s| #.*||g; /^#/s|#.*||g;" \ -e "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \ -e "s|^\($s\)\($w\)${s}[:-]$s\(.*\)$s\$|\1$fs\2$fs\3|p" | awk -F"$fs" '{ indent = length($1)/2; if (length($2) == 0) { conj[indent]="+";} else {conj[indent]="";} vname[indent] = $2; for (i in vname) {if (i > indent) {delete vname[i]}} if (length($3) > 0) { vn=""; for (i=0; i"$TEMPDIR/vdoconf.yml" VDONAME=$(awk -v DNAME="$DEVICE" '/.*VDOService$/ {VNAME=substr($1, 0, length($1) - 1)} /[[:space:]]*device:/ { if ($2 ~ DNAME) {print VNAME}}' "$TEMPDIR/vdoconf.yml") TRVDONAME=$(echo "$VDONAME" | tr '-' '_') # When VDO volume is 'active', check it's not mounted/being used eval "$(dmsetup info -c -o open "$VDONAME" --noheadings --nameprefixes || true)" test "${DM_OPEN:-0}" -eq 0 || error "Cannot converted VDO volume \"$VDONAME\" which is in use!" #parse_yaml_ "$TEMPDIR/vdoconf.yml" _ eval "$(parse_yaml_ "$TEMPDIR/vdoconf.yml" _ | grep "$TRVDONAME" | sed -e "s/_config_vdos_$TRVDONAME/vdo/g")" vdo_logicalSize=$(get_kb_size_with_unit_ "$vdo_logicalSize") vdo_physicalSize=$(get_kb_size_with_unit_ "$vdo_physicalSize") verbose "Going to convert physical sized VDO device $vdo_physicalSize KiB." verbose "With logical volume of size $vdo_logicalSize KiB." PARAMS=$(cat <