#!/bin/bash
#
# pvsqueeze - Squeeze PEs into free holes
#
# This script iteratively moves linear (allocated to LV) PEs to fill in free
# holes found in a PV segement map. The end result is that all free PEs are
# at the end of the PV. Use this script as required in anger!
#

# Usage
if [[ ! $1 =~ ^/dev/ ]]; then
  echo "Usage: $0 PV"
  exit 1
fi

# Set some vars
pvdev=$1
pvsargs='--noheadings --nameprefixes --segments -o pvseg_start,pvseg_size,segtype'

# Show current segment map
echo 'PV segment map before squeeze:'
pvs "$pvdev" --segments -o lvname,pvseg_start,pvseg_size,seg_le_ranges
if [[ $? -ne 0 ]]; then
  echo "$0: Fatal error getting PV info"
  exit 1
fi

# Iterate stuffing PEs in each free hole
while true; do
  # Find first free segment
  LVM2_PVSEG_START=
  eval $(pvs "$pvdev" $pvsargs | grep SEGTYPE=.free. | head -1)
  if [[ -z $LVM2_PVSEG_START ]]; then
    echo "$0: No free segments found"
    exit 1
  fi
  free_start=$LVM2_PVSEG_START
  free_size=$LVM2_PVSEG_SIZE

  # Find the last linear segment
  LVM2_PVSEG_START=
  eval $(pvs "$pvdev" $pvsargs | grep SEGTYPE=.linear. | tail -1)
    if [[ -z $LVM2_PVSEG_START ]]; then
    echo "$0: No linear segments found"
    exit 1
  fi
  move_start=$LVM2_PVSEG_START
  move_size=$LVM2_PVSEG_SIZE

  # We're done when first free is after the last linear
  [[ $free_start -gt $move_start ]] && break

  # Move last linear PEs to the free segment
  from_range="$pvdev:$((move_start+move_size-free_size))-$((move_start+move_size-1))"
  to_range="$pvdev:$((free_start))-$((free_start+free_size))"
  echo "Moving $from_range to $to_range..."
  pvmove --atomic --alloc anywhere "$from_range" "$to_range"
  if [[ $? -ne 0 ]]; then
    echo "$0: Fatal error doing pvmove"
    exit 1
  fi
done

# Show squeezed segment map
echo 'PV segment map after squeeze:'
pvs "$pvdev" --segments -o lvname,pvseg_start,pvseg_size,seg_le_ranges

exit 0
