#!/bin/bash

#--
# Copyright 2014-2017 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#++

#  Purpose:  This script sets up the storage for container runtimes.
#  Author:   Andy Grimm <agrimm@redhat.com>

set -e

# container-storage-setup version information
_CSS_MAJOR_VERSION="0"
_CSS_MINOR_VERSION="11"
_CSS_SUBLEVEL="0"
_CSS_EXTRA_VERSION=""

_CSS_VERSION="${_CSS_MAJOR_VERSION}.${_CSS_MINOR_VERSION}.${_CSS_SUBLEVEL}"
[ -n "$_CSS_EXTRA_VERSION" ] && _CSS_VERSION="${_CSS_VERSION}-${_CSS_EXTRA_VERSION}"

# Locking related
_LOCKFD=300
_LOCKDIR="/var/lock/container-storage-setup"
_LOCKFILE="lock"

_CONFIG_NAME=""
_CONFIG_DIR="/var/lib/container-storage-setup/"

# Partition type related
_MAX_MBR_SIZE_BYTES="2199023255040"

# Metadata related stuff
_METADATA_VERSION=1
_INFILE_NAME="infile"
_OUTFILE_NAME="outfile"
_METAFILE_NAME="metadata"
_STATUSFILE_NAME="status"

# This section reads the config file $INPUTFILE
# Read man page for a description of currently supported options:
# 'man container-storage-setup'

_DOCKER_ROOT_LV_NAME="docker-root-lv"
_DOCKER_ROOT_DIR="/var/lib/docker"
_DOCKER_METADATA_DIR="/var/lib/docker"
DOCKER_ROOT_VOLUME_SIZE=40%FREE

_DOCKER_COMPAT_MODE=""
_STORAGE_IN_FILE=""
_STORAGE_OUT_FILE=""
_STORAGE_DRIVERS="devicemapper overlay overlay2"

# Command related variables
_COMMAND_LIST="create activate deactivate remove list export add-dev"
_COMMAND=""

_PIPE1=/run/css-$$-fifo1
_PIPE2=/run/css-$$-fifo2
_TEMPDIR=$(mktemp --tmpdir -d)

# Keeps track of resolved device paths
_DEVS_RESOLVED=""

# Will have currently configured storage options in ${_STORAGE_OUT_FILE}
_CURRENT_STORAGE_OPTIONS=""

_STORAGE_OPTIONS="STORAGE_OPTIONS"

# Keeps track of if we created a volume group or not.
_VG_CREATED=

get_docker_version() {
  local version

  # docker version command exits with error as daemon is not running at this
  # point of time. So continue despite the error.
  version=`docker version --format='{{.Client.Version}}' 2>/dev/null` || true
  echo $version
}

get_deferred_removal_string() {
  local version major minor

  if ! version=$(get_docker_version);then
    return 0
  fi
  [ -z "$version" ] && return 0

  major=$(echo $version | cut -d "." -f1)
  minor=$(echo $version | cut -d "." -f2)
  [ -z "$major" ] && return 0
  [ -z "$minor" ] && return 0

  # docker 1.7 onwards supports deferred device removal. Enable it.
  if [ $major -gt 1 ] ||  ([ $major -eq 1 ] && [ $minor -ge 7 ]);then
    echo "--storage-opt dm.use_deferred_removal=true"
  fi
}

get_deferred_deletion_string() {
  local version major minor

  if ! version=$(get_docker_version);then
    return 0
  fi
  [ -z "$version" ] && return 0

  major=$(echo $version | cut -d "." -f1)
  minor=$(echo $version | cut -d "." -f2)
  [ -z "$major" ] && return 0
  [ -z "$minor" ] && return 0

  if should_enable_deferred_deletion $major $minor; then
     echo "--storage-opt dm.use_deferred_deletion=true"
  fi
}

should_enable_deferred_deletion() {
   # docker 1.9 onwards supports deferred device deletion. Enable it.
   local major=$1
   local minor=$2
   if [ $major -lt 1 ] || ([ $major -eq 1 ] && [ $minor -lt 9 ]);then
      return 1
   fi
   if platform_supports_deferred_deletion; then
      return 0
   fi
   return 1
}

platform_supports_deferred_deletion() {
        local deferred_deletion_supported=1
        trap cleanup_pipes EXIT
        local child_exec="$_SRCDIR/css-child-read-write.sh"

        [ ! -x "$child_exec" ] && child_exec="/usr/share/container-storage-setup/css-child-read-write"

        if [ ! -x "$child_exec" ];then
           return 1
        fi
        mkfifo $_PIPE1
        mkfifo $_PIPE2
        unshare -m ${child_exec} $_PIPE1 $_PIPE2 "$_TEMPDIR" &
        read -t 10 n <>$_PIPE1
        if [ "$n" != "start" ];then
	   return 1
        fi
        rmdir $_TEMPDIR > /dev/null 2>&1
        deferred_deletion_supported=$?
        echo "finish" > $_PIPE2
        return $deferred_deletion_supported
}

cleanup_pipes(){
    rm -f $_PIPE1
    rm -f $_PIPE2
    rmdir $_TEMPDIR 2>/dev/null
}

extra_options_has_dm_fs() {
  local option
  for option in ${EXTRA_STORAGE_OPTIONS}; do
    if grep -q "dm.fs=" <<< $option; then
      return 0
    fi
  done
  return 1
}

# Given a dm device name in /dev/mapper/ dir
# (ex. /dev/mapper/docker-vg--docker-pool), get associated volume group
get_dmdev_vg() {
  local dmdev=${1##"/dev/mapper/"}
  local vg

  vg=`dmsetup splitname $dmdev --noheadings | cut -d ":" -f1`
  echo $vg
}

# Wait for a device for certain time interval. If device is found 0 is
# returned otherwise 1.
wait_for_dev() {
  local devpath=$1
  local timeout=$DEVICE_WAIT_TIMEOUT

  if [ -b "$devpath" ];then
    Info "Device node $devpath exists."
    return 0
  fi

  if [ -z "$DEVICE_WAIT_TIMEOUT" ] || [ "$DEVICE_WAIT_TIMEOUT" == "0" ];then
    Info "Not waiting for device $devpath as DEVICE_WAIT_TIMEOUT=${DEVICE_WAIT_TIMEOUT}."
    return 0
  fi

  while [ $timeout -gt 0 ]; do
    Info "Waiting for device $devpath to be available. Wait time remaining is $timeout seconds"
    if [ $timeout -le 5 ];then
      sleep $timeout
    else
      sleep 5
    fi
    timeout=$((timeout-5))
    if [ -b "$devpath" ]; then
      Info "Device node $devpath exists."
      return 0
    fi
  done

  Info "Timed out waiting for device $devpath"
  return 1
}

get_devicemapper_config_options() {
  local storage_options
  local dm_fs="--storage-opt dm.fs=xfs"

  # docker expects device mapper device and not lvm device. Do the conversion.
  eval $( lvs --nameprefixes --noheadings -o lv_name,kernel_major,kernel_minor $VG | while read line; do
    eval $line
    if [ "$LVM2_LV_NAME" = "$CONTAINER_THINPOOL" ]; then
      echo _POOL_DEVICE_PATH=/dev/mapper/$( cat /sys/dev/block/${LVM2_LV_KERNEL_MAJOR}:${LVM2_LV_KERNEL_MINOR}/dm/name )
    fi
  done )

  if extra_options_has_dm_fs; then
    # dm.fs option defined in ${EXTRA_STORAGE_OPTIONS}
    dm_fs=""
  fi

  storage_options="${_STORAGE_OPTIONS}=\"--storage-driver devicemapper ${dm_fs} --storage-opt dm.thinpooldev=$_POOL_DEVICE_PATH $(get_deferred_removal_string) $(get_deferred_deletion_string) ${EXTRA_STORAGE_OPTIONS}\""
  echo $storage_options
}

get_config_options() {
  if [ "$1" == "devicemapper" ]; then
    get_devicemapper_config_options
    return $?
  fi
  echo "${_STORAGE_OPTIONS}=\"--storage-driver $1 ${EXTRA_STORAGE_OPTIONS}\""
  return 0
}

# Check if multiple overlay directories are supported and if overlay module
# itself is supported on the system.
can_mount_overlay() {
  local dir="/run/container-storage-setup/"
  local lower1="$dir/lower1"
  local lower2="$dir/lower2"
  local upper="$dir/upper"
  local work="$dir/work"
  local merged="$dir/merged"
  local cmd

  # Create multiple directories in /run
  if ! mkdir -p $dir; then
    Error "Failed to create directory $dir"
    return 1
  fi

  cmd="mkdir -p $lower1 $lower2 $upper $work $merged"
  if ! $cmd; then
    Error "Failed to run $cmd"
    return 1
  fi

  cmd="unshare -m mount -t overlay -o lowerdir=$lower1:$lower2,upperdir=$upper,workdir=$work none $merged"
  if ! $cmd; then
    Error "Failed to run $cmd"
    return 1
  fi

  return 0
}

is_xfs_ftype_enabled() {
  local mountroot=$1
  local fstype

  fstype=$(stat -f -c '%T' $mountroot)

  [ "$fstype" != "xfs" ] && return 0

  # For xfs, see https://bugzilla.redhat.com/show_bug.cgi?id=1288162#c8
  if test "$(xfs_info $mountroot | grep -o 'ftype=[01]')" = "ftype=0"; then
	  return 1
  fi

  return 0
}

write_storage_config_file () {
  local storage_driver=$1
  local storage_out_file=$2
  local storage_options

  if [ -z "$storage_driver" ];then
    touch "$storage_out_file"
    return 0
  fi

  if ! storage_options=$(get_config_options $storage_driver); then
      return 1
  fi

  cat <<EOF > ${storage_out_file}.tmp
$storage_options
EOF

  mv -Z ${storage_out_file}.tmp ${storage_out_file}
}

convert_size_in_bytes() {
  local size=$1 prefix suffix

  # if it is all numeric, it is valid as by default it will be MiB.
  if [[ $size =~ ^[[:digit:]]+$ ]]; then
    echo $(($size*1024*1024))
    return 0
  fi

  # supprt G, G[bB] or Gi[bB] inputs.
  prefix=${size%[bBsSkKmMgGtTpPeE]i[bB]}
  prefix=${prefix%[bBsSkKmMgGtTpPeE][bB]}
  prefix=${prefix%[bBsSkKmMgGtTpPeE]}

  # if prefix is not all numeric now, it is an error.
  if ! [[ $prefix =~ ^[[:digit:]]+$ ]]; then
    return 1
  fi

  suffix=${data_size#$prefix}

  case $suffix in
    b*|B*) echo $prefix;;
    s*|S*) echo $(($prefix*512));;
    k*|K*) echo $(($prefix*2**10));;
    m*|M*) echo $(($prefix*2**20));;
    g*|G*) echo $(($prefix*2**30));;
    t*|T*) echo $(($prefix*2**40));;
    p*|P*) echo $(($prefix*2**50));;
    e*|E*) echo $(($prefix*2**60));;
    *) return 1;;
  esac
}

data_size_in_bytes() {
  local data_size=$1
  local bytes vg_size free_space percent

  # -L compatible syntax
  if [[ $DATA_SIZE != *%* ]]; then
    bytes=`convert_size_in_bytes $data_size`
    [ $? -ne 0 ] && return 1
    # If integer overflow took place, value is too large to handle.
    if [ $bytes -lt 0 ];then
      Error "DATA_SIZE=$data_size is too large to handle."
      return 1
    fi
    echo $bytes
    return 0
  fi

  if [[ $DATA_SIZE == *%FREE ]];then
    free_space=$(vgs --noheadings --nosuffix --units b -o vg_free $VG)
    percent=${DATA_SIZE%\%FREE}
    echo $((percent*free_space/100))
    return 0
  fi

  if [[ $DATA_SIZE == *%VG ]];then
    vg_size=$(vgs --noheadings --nosuffix --units b -o vg_size $VG)
    percent=${DATA_SIZE%\%VG}
    echo $((percent*vg_size/100))
  fi
  return 0
}

check_min_data_size_condition() {
  local min_data_size_bytes data_size_bytes free_space

  [ -z $MIN_DATA_SIZE ] && return 0

  if ! check_numeric_size_syntax $MIN_DATA_SIZE; then
    Fatal "MIN_DATA_SIZE value $MIN_DATA_SIZE is invalid."
  fi

  if ! min_data_size_bytes=$(convert_size_in_bytes $MIN_DATA_SIZE);then
    Fatal "Failed to convert MIN_DATA_SIZE to bytes"
  fi

  # If integer overflow took place, value is too large to handle.
  if [ $min_data_size_bytes -lt 0 ];then
    Fatal "MIN_DATA_SIZE=$MIN_DATA_SIZE is too large to handle."
  fi

  free_space=$(vgs --noheadings --nosuffix --units b -o vg_free $VG)

  if [ $free_space -lt $min_data_size_bytes ];then
    Fatal "There is not enough free space in volume group $VG to create data volume of size MIN_DATA_SIZE=${MIN_DATA_SIZE}."
  fi

  if ! data_size_bytes=$(data_size_in_bytes $DATA_SIZE);then
    Fatal "Failed to convert desired data size to bytes"
  fi

  if [ $data_size_bytes -lt $min_data_size_bytes ]; then
    # Increasing DATA_SIZE to meet minimum data size requirements.
    Info "DATA_SIZE=${DATA_SIZE} is smaller than MIN_DATA_SIZE=${MIN_DATA_SIZE}. Will create data volume of size specified by MIN_DATA_SIZE."
    DATA_SIZE=$MIN_DATA_SIZE
  fi
}

create_lvm_thin_pool () {
  if [ -z "$_DEVS_RESOLVED" ] && [ -z "$_VG_EXISTS" ]; then
    Fatal "Specified volume group $VG does not exist, and no devices were specified"
  fi

  if [ ! -n "$DATA_SIZE" ]; then
    Fatal "DATA_SIZE not specified."
  fi

  if ! check_data_size_syntax $DATA_SIZE; then
    Fatal "DATA_SIZE value $DATA_SIZE is invalid."
  fi

  check_min_data_size_condition

  if [ -n "$POOL_META_SIZE" ]; then
    _META_SIZE_ARG="$POOL_META_SIZE"
  else
  # Calculate size of metadata lv. Reserve 0.1% of the free space in the VG
  # for docker metadata.
  _VG_SIZE=$(vgs --noheadings --nosuffix --units s -o vg_size $VG)
  _META_SIZE=$(( $_VG_SIZE / 1000 + 1 ))
  if [ -z "$_META_SIZE" ];then
    Fatal "Failed to calculate metadata volume size."
  fi
  _META_SIZE_ARG=${_META_SIZE}s
  fi

  if [ -n "$CHUNK_SIZE" ]; then
    _CHUNK_SIZE_ARG="-c $CHUNK_SIZE"
  fi

  if [[ $DATA_SIZE == *%* ]]; then
    _DATA_SIZE_ARG="-l $DATA_SIZE"
  else
    _DATA_SIZE_ARG="-L $DATA_SIZE"
  fi

  lvcreate -y --type thin-pool --zero n $_CHUNK_SIZE_ARG --poolmetadatasize $_META_SIZE_ARG $_DATA_SIZE_ARG -n $CONTAINER_THINPOOL $VG
}

get_configured_thin_pool() {
  local options tpool opt

  options=$_CURRENT_STORAGE_OPTIONS
  [ -z "$options" ] && return 0

  # This assumes that thin pool is specified as dm.thinpooldev=foo. There
  # are no spaces in between.
  for opt in $options; do
    if [[ $opt =~ dm.thinpooldev* ]];then
      tpool=${opt#*=}
      echo "$tpool"
      return 0
    fi
  done
}

check_docker_storage_metadata() {
  local docker_devmapper_meta_dir="$_DOCKER_METADATA_DIR/devicemapper/metadata/"

  [ ! -d "$docker_devmapper_meta_dir" ] && return 0

  # Docker seems to be already using devicemapper storage driver. Error out.
  Error "Docker has been previously configured for use with devicemapper graph driver. Not creating a new thin pool as existing docker metadata will fail to work with it. Manual cleanup is required before this will succeed."
  Info "Docker state can be reset by stopping docker and by removing ${_DOCKER_METADATA_DIR} directory. This will destroy existing docker images and containers and all the docker metadata."
  exit 1
}

systemd_escaped_filename () {
  local escaped_path filename path=$1
  escaped_path=$(echo ${path}|sed 's|-|\\x2d|g')
  filename=$(echo ${escaped_path}.mount|sed 's|/|-|g' | cut -b 2-)
  echo $filename
}


# Compatibility mode code
run_docker_compatibility_code() {
  # Verify storage options set correctly in input files
  check_storage_options

  # Query and save current storage options
  if ! _CURRENT_STORAGE_OPTIONS=$(get_current_storage_options); then
    return 1
  fi

  determine_rootfs_pvs_vg

  if [ $_RESET -eq 1 ]; then
    reset_storage_compat
    exit 0
  fi

  partition_disks_create_vg
  grow_root_pvs

  # NB: We are growing root here first, because when root and docker share a
  # disk, we'll default to giving some portion of remaining space to docker.
  # Do this operation only if root is on a logical volume.
  [ -n "$_ROOT_VG" ] && grow_root_lv_fs

  if is_old_data_meta_mode; then
    Fatal "Old mode of passing data and metadata logical volumes to docker is not supported. Exiting."
  fi

  setup_storage_compat
}

#
# In the past we created a systemd mount target file, we no longer
# use it, but if one pre-existed we still need to handle it.
#
remove_systemd_mount_target () {
  local mp=$1
  local filename=$(systemd_escaped_filename $mp)
  if [ -f /etc/systemd/system/$filename ]; then
      if [ -x /usr/bin/systemctl ];then      
	  systemctl disable $filename >/dev/null 2>&1
	  systemctl stop $filename >/dev/null 2>&1
	  systemctl daemon-reload
      fi
    rm -f /etc/systemd/system/$filename >/dev/null 2>&1
  fi
}

# This is used in compatibility mode.
reset_extra_volume_compat () {
  local mp filename
  local lv_name=$1
  local mount_dir=$2
  local vg=$3

  if extra_volume_exists $lv_name $vg; then
    mp=$(extra_lv_mountpoint $vg $lv_name $mount_dir)
    if [ -n "$mp" ];then
      if ! umount $mp >/dev/null 2>&1; then
        Fatal "Failed to unmount $mp"
      fi
    fi
    lvchange -an $vg/${lv_name}
    lvremove $vg/${lv_name}
  else
    return 0
  fi
  # If the user has manually unmounted mount directory, mountpoint (mp)
  # will be empty. Extract ${mp} from $(mount_dir) in that case.
  if [ -z "$mp" ];then
    mp=${mount_dir}
  fi
  remove_systemd_mount_target $mp
}

reset_lvm_thin_pool () {
  local thinpool_name=$1
  local vg=$2
  if lvm_pool_exists $thinpool_name $vg; then
      lvchange -an $vg/${thinpool_name}
      lvremove $vg/${thinpool_name}
  fi
}

# Used in compatibility mode. Determine if already configured thin pool
# is managed by container-storage-setup or not. Returns 0 if tpool is
# managed otherwise 1.
is_managed_tpool_compat() {
  local tpool=$1
  local thinpool_name=${CONTAINER_THINPOOL}
  local escaped_pool_lv_name=`echo $thinpool_name | sed 's/-/--/g'`

  # css generated thin pool device name starts with /dev/mapper/ and
  # ends with $thinpool_name
  [[ "$tpool" == /dev/mapper/*${escaped_pool_lv_name} ]] && return 0
  return 1
}

# This is used in comatibility mode.
bringup_existing_thin_pool_compat() {
  local tpool=$1

   # css generated thin pool device name starts with /dev/mapper/ and
   # ends with $thinpool_name
   if ! is_managed_tpool_compat "$tpool";then
     Fatal "Thin pool ${tpool} does not seem to be managed by container-storage-setup. Exiting."
   fi

  if ! wait_for_dev "$tpool"; then
    Fatal "Already configured thin pool $tpool is not available. If thin pool exists and is taking longer to activate, set DEVICE_WAIT_TIMEOUT to a higher value and retry. If thin pool does not exist any more, remove ${_STORAGE_OUT_FILE} and retry"
  fi
}

# This is used in comatibility mode. Returns 0 if thin pool is already
# configured and wait could find the device. Returns 1 if thin pool is
# not configured and probably needs to be created. Terminates script
# on fatal errors.
check_existing_thinpool_compat() {
  local tpool

  # Check if a thin pool is already configured in /etc/sysconfig/docker-storage
  # If yes, wait for that thin pool to come up.
  tpool=`get_configured_thin_pool`
  [ -z "$tpool" ] && return 1

  Info "Found an already configured thin pool $tpool in ${_STORAGE_OUT_FILE}"
  bringup_existing_thin_pool_compat "$tpool"
  return
}

# This is used in comatibility mode.
setup_lvm_thin_pool_compat () {
  local thinpool_name=${CONTAINER_THINPOOL}

  if check_existing_thinpool_compat; then
    process_auto_pool_extenion ${VG} ${thinpool_name}
    # We found existing thin pool and waited for it and processed auto
    # pool extension changes. There should not be any need to process
    # further
    return
  fi

  # At this point of time, a volume group should exist for lvm thin pool
  # operations to succeed. Make that check and fail if that's not the case.
  if ! vg_exists "$VG";then
    Fatal "No valid volume group found. Exiting."
  else
    _VG_EXISTS=1
  fi

  if ! lvm_pool_exists $thinpool_name $VG; then
    [ -n "$_DOCKER_COMPAT_MODE" ] && check_docker_storage_metadata
    create_lvm_thin_pool
    [ -n "$_STORAGE_OUT_FILE" ] &&  write_storage_config_file $STORAGE_DRIVER "$_STORAGE_OUT_FILE"
  else
    # At this point /etc/sysconfig/docker-storage file should exist. If user
    # deleted this file accidently without deleting thin pool, recreate it.
    if [ -n "$_STORAGE_OUT_FILE" -a ! -f "${_STORAGE_OUT_FILE}" ];then
      Info "${_STORAGE_OUT_FILE} file is missing. Recreating it."
      write_storage_config_file $STORAGE_DRIVER "$_STORAGE_OUT_FILE"
    fi
  fi

  process_auto_pool_extenion ${VG} ${thinpool_name}
}

lvm_pool_exists() {
  local lv_data
  local lvname lv lvsize
  local thinpool_name=$1
  local vg=$2

  if [ -z "$thinpool_name" ]; then
      Fatal "Thin pool name must be specified."
  fi
  lv_data=$( lvs --noheadings -o lv_name,lv_attr --separator , $vg | sed -e 's/^ *//')
  SAVEDIFS=$IFS
  for lv in $lv_data; do
  IFS=,
  read lvname lvattr <<< "$lv"
    # pool logical volume has "t" as first character in its attributes
    if [ "$lvname" == "$thinpool_name" ] && [[ $lvattr == t* ]]; then
      IFS=$SAVEDIFS
      return 0
    fi
  done
  IFS=$SAVEDIFS

  return 1
}

# If a ${_STORAGE_OUT_FILE} file is present and if it contains
# dm.datadev or dm.metadatadev entries, that means we have used old mode
# in the past.
is_old_data_meta_mode() {
  if [ ! -f "${_STORAGE_OUT_FILE}" ];then
    return 1
  fi

  if ! grep -e "^${_STORAGE_OPTIONS}=.*dm\.datadev" -e "^${_STORAGE_OPTIONS}=.*dm\.metadatadev" ${_STORAGE_OUT_FILE}  > /dev/null 2>&1;then
    return 1
  fi

  return 0
}

grow_root_pvs() {
  # If root is not in a volume group, then there are no root pvs and nothing
  # to do.
  [ -z "$_ROOT_PVS" ] && return 0

  # Grow root pvs only if user asked for it through config file.
  [ "$GROWPART" != "true" ] && return

  if [ ! -x "/usr/bin/growpart" ];then
    Error "GROWPART=true is specified and /usr/bin/growpart executable is not available. Install /usr/bin/growpart and try again."
    return 1
  fi

  # Note that growpart is only variable here because we may someday support
  # using separate partitions on the same disk.  Today we fail early in that
  # case.  Also note that the way we are doing this, it should support LVM
  # RAID for the root device.  In the mirrored or striped case, we are growing
  # partitions on all disks, so as long as they match, growing the LV should
  # also work.
  for pv in $_ROOT_PVS; do
    # Split device & partition.  Ick.
    growpart $( echo $pv | sed -r 's/([^0-9]*)([0-9]+)/\1 \2/' ) || true
    pvresize $pv
  done
}

grow_root_lv_fs() {
  if [ -n "$ROOT_SIZE" ]; then

    # Allow user to pass in an argument that could be provided
    # to -L (like 10G or +5G) or an argument that could be passed
    # to -l (like 80%FREE). Switch based on if '%' is in string.
    local root_size_arg
    if [[ $ROOT_SIZE =~ % ]]; then
        root_size_arg="-l $ROOT_SIZE"
    else
        root_size_arg="-L $ROOT_SIZE"
    fi

    # TODO: Error checking if specified size is <= current size
    lvextend -r $root_size_arg $_ROOT_DEV || true
  fi
}

# Determines if a device is already added in a volume group as pv. Returns
# 0 on success.
is_dev_part_of_vg() {
  local dev=$1
  local vg=$2

  if ! pv_name=$(pvs --noheadings -o pv_name -S pv_name=$dev,vg_name=$vg); then
    Fatal "Error running command pvs. Exiting."
  fi

 [ -z "$pv_name" ] && return 1
 pv_name=`echo $pv_name | tr -d '[ ]'`
 [ "$pv_name" == "$dev" ] && return 0
 return 1
}

is_block_dev_partition() {
  local bdev=$1 devparent

  if ! disktype=$(lsblk -n --nodeps --output type ${bdev}); then
    Fatal "Failed to run lsblk on device $bdev"
  fi

  if [ "$disktype" == "part" ];then
    return 0
  fi

  if [ "$disktype" == "mpath" ];then
    return 1
  fi

  # For loop device partitions, lsblk reports type as "loop" and not "part".
  # So check if device has a parent in the tree and if it does, there are high
  # chances it is partition (except the case of lvm volumes)
  if ! devparent=$(lsblk -npls -o NAME ${bdev}|tail -n +2); then
    Fatal "Failed to run lsblk on device $bdev"
  fi

  if [ -n "$devparent" ];then
    return 0
  fi

  return 1
}

check_wipe_block_dev_sig() {
  local bdev=$1
  local sig

  if ! sig=$(wipefs -p $bdev); then
    Fatal "Failed to check signatures on device $bdev"
  fi

  [ "$sig" == "" ] && return 0

  if [ "$WIPE_SIGNATURES" == "true" ];then
    Info "Wipe Signatures is set to true. Any signatures on $bdev will be wiped."
    if ! wipefs -a $bdev; then
      Fatal "Failed to wipe signatures on device $bdev"
    fi
    return 0
  fi

  while IFS=, read offset uuid label type; do
    [ "$offset" == "# offset" ] && continue
    Fatal "Found $type signature on device ${bdev} at offset ${offset}. Wipe signatures using wipefs or use WIPE_SIGNATURES=true and retry."
  done <<< "$sig"
}

# This is used in compatibility mode
canonicalize_block_devs_compat() {
  local devs=$1 dev
  local devs_abs dev_abs
  local dest_dev

  for dev in ${devs}; do
    # If the device name is a symlink, follow it and use the target
    if [ -h "$dev" ];then
      if ! dest_dev=$(readlink -e $dev);then
        Fatal "Failed to resolve symbolic link $dev"
      fi
      dev=$dest_dev
    fi
    # Looks like we allowed just device name (sda) as valid input. In
    # such cases /dev/$dev should be a valid block device.
    dev_abs=$dev
    [ ! -b "$dev" ] && dev_abs="/dev/$dev"
    [ ! -b "$dev_abs" ] && Fatal "$dev_abs is not a valid block device."

    if is_block_dev_partition ${dev_abs}; then
      Fatal "Partition specification unsupported at this time."
    fi
    devs_abs="$devs_abs $dev_abs"
  done

  # Return list of devices to caller.
  echo "$devs_abs"
}

# This is used in config creation mode
canonicalize_block_devs_generic() {
  local devs=$1 dev
  local devs_resolved resolved_device

  for dev in ${devs}; do
    if ! resolved_device=$(realpath -e $dev);then
        Fatal "Failed to resolve path for device ${dev}"
    fi

    [ ! -b "$resolved_device" ] && Fatal "$resolved_device is not a valid block device."

    if is_block_dev_partition ${resolved_device}; then
      Fatal "Partition specification unsupported at this time."
    fi
    if [ -n "$devs_resolved" ]; then
      devs_resolved="$devs_resolved $resolved_device"
    else
      devs_resolved="$resolved_device"
    fi
  done

  # Return list of devices to caller.
  echo "$devs_resolved"
}

# Make sure passed in devices are valid block devies. Also make sure they
# are not partitions. Names which are of the form "sdb", convert them to
# their absolute path for processing in rest of the script.
canonicalize_block_devs() {
  local input_dev_list="$1"
  local devs_list

  if [ "$_DOCKER_COMPAT_MODE" == "1" ];then
    devs_list=$(canonicalize_block_devs_compat "$input_dev_list") || return 1
  else
    devs_list=$(canonicalize_block_devs_generic "$input_dev_list") || return 1
  fi
  echo $devs_list
}

# Scans all the disks listed in DEVS= and returns the disks which are not
# already part of volume group and are new and require further processing.
scan_disks() {
  local disk_list="$1"
  local vg=$2
  local wipe_signatures=$3
  local new_disks=""

  for dev in $disk_list; do
    local part=$(dev_query_first_child $dev)

    if [ -n "$part" ] && is_dev_part_of_vg ${part} $vg; then
      Info "Device ${dev} is already partitioned and is part of volume group $VG"
      continue
    fi

    # If signatures are being overridden, then simply return the disk as new
    # disk. Even if it is partitioned, partition signatures will be wiped.
    if [ "$wipe_signatures" == "true" ];then
      new_disks="$new_disks $dev"
      continue
    fi

    # If device does not have partitions, it is a new disk requiring processing.
    if [[ -z "$part" ]]; then
      new_disks="$dev $new_disks"
      continue
    fi

    Fatal "Device $dev is already partitioned and cannot be added to volume group $vg"
  done

  echo $new_disks
}

determine_partition_type() {
  local dev="$1" size_bytes part_type

  if ! size_bytes=$(blockdev --getsize64 "$dev"); then
    Fatal "Failed to determine size of disk $dev"
  fi

  if [ $size_bytes -gt $_MAX_MBR_SIZE_BYTES ];then
    part_type="gpt"
  else
    part_type="dos"
  fi
  echo $part_type
}

create_partition_sfdisk(){
  local dev="$1" part_type="$2" size part_label
  # Use a single partition of a whole device
  # TODO:
  #   * Consider gpt, or unpartitioned volumes
  #   * Error handling when partition(s) already exist
  #   * Deal with loop/nbd device names. See growpart code
  if [ "$part_type" == "gpt" ];then
    # Linux LVM GUID for GPT. Taken from Wiki.
    part_label="E6D6D379-F507-44C2-A23C-238F2A3DF928"
    # Create as big a partition as possible.
    size=""
  else
    part_label="8e"
    size=$(( $( awk "\$4 ~ /"$( basename $dev )"/ { print \$3 }" /proc/partitions ) * 2 - 2048 ))
  fi
    cat <<EOF | sfdisk $dev
unit: sectors
label: $part_type
2048,${size},$part_label
EOF
}

create_partition_parted(){
  local dev="$1"
  local part_type="$2"

  if [ "$part_type" == "gpt" ];then
    parted $dev --script mklabel gpt mkpart "container-partition" 0% 100% set 1 lvm on
  else
    parted $dev --script mklabel msdos mkpart primary 0% 100% set 1 lvm on
  fi
}

create_partition() {
  local dev="$1" part part_type

  part_type=`determine_partition_type "$dev"`

  if [ -x "/usr/sbin/parted" ]; then
    create_partition_parted $dev "$part_type"
  else
    create_partition_sfdisk $dev "$part_type"
  fi

  # Sometimes on slow storage it takes a while for partition device to
  # become available. Wait for device node to show up.
  if ! udevadm settle;then
    Fatal "udevadm settle after partition creation failed. Exiting."
  fi

  part=$(dev_query_first_child $dev)

  if ! wait_for_dev ${part}; then
    Fatal "Partition device ${part} is not available"
  fi
}

dev_query_first_child() {
  lsblk -npl -o NAME "$1" | tail -n +2 | head -1
}

create_disk_partitions() {
  local devs="$1" part

  for dev in $devs; do
    # wipefs /dev/disk does not wipe any lvm signatures which might be
    # present on /dev/diskpart1. This signature will become visible to
    # lvm udev rules and will kickstart volume creation as soon as partion
    # is created and race with further partition commands like wipefs,
    # pvcreate etc. So zero out first few MB of disk in an attempt to
    # wipe any lvm signatures on first partition.
    #
    # By now we have ownership of disk and we have checked there are no
    # signatures on disk or signatures have been wiped. Dont care about
    # any signatures now on in the middle of disk.
    Info "Writing zeros to first 4MB of device $dev"
    if ! dd if=/dev/zero of=$dev bs=1M count=4; then
      Fatal "Failed to zero first 4MB of device $bdev"
    fi

    create_partition $dev
    part=$(dev_query_first_child $dev)

    # It now seems unnecessary to do wipefs on partition given we already
    # zeroed out first 4MB. Only time it will be required if partition
    # starts beyong 4MB. Keep it for now.
    if ! wipefs -f -a ${part}; then
      Fatal "Failed to wipe signatures on device ${part}"
    fi
    pvcreate ${part}
    _PVS="$_PVS ${part}"
  done
}

remove_partition() {
  local dev="$1"

  if [ -x "/usr/sbin/parted" ]; then
    parted "$dev" rm 1 >/dev/null
  else
    sfdisk --delete "$dev" 1 >/dev/null
  fi
}

# Remove disk pvs and partitions. This is called in reset storage path.
# If partition or pv does not exist, it will still return success. Error
# will be returned only if pv or partition exists and removal fails.
remove_disk_pvs_parts() {
  local devs="$1" part

  for dev in $devs; do
    part=$(dev_query_first_child $dev)
    [ -z "$part" ] && continue

    if ! remove_pv_if_exists $part; then
      Error "Failed to remove physical volume label on device $part"
      return 1
    fi

    if ! remove_partition $dev; then
      Error "Failed to remove partition on device $dev"
      return 1
    fi
  done
}

create_extend_volume_group() {
  if [ -z "$_VG_EXISTS" ]; then
    vgcreate $VG $_PVS
    _VG_CREATED=1
    _VG_EXISTS=1
  else
    # TODO:
    #   * Error handling when PV is already part of a VG
    vgextend $VG $_PVS
  fi
}

# Auto extension logic. Create a profile for pool and attach that profile
# the pool volume.
enable_auto_pool_extension() {
  local volume_group=$1
  local pool_volume=$2
  local profileName="${volume_group}--${pool_volume}-extend"
  local profileFile="${profileName}.profile"
  local profileDir
  local tmpFile=`mktemp -p /run -t tmp.XXXXX`

  profileDir=$(lvm dumpconfig --type full | grep "profile_dir" | cut -d "=" -f2 | sed 's/"//g')
  [ -n "$profileDir" ] || return 1

  if [ ! -n "$POOL_AUTOEXTEND_THRESHOLD" ];then
    Error "POOL_AUTOEXTEND_THRESHOLD not specified"
    return 1
  fi

  if [ ! -n "$POOL_AUTOEXTEND_PERCENT" ];then
    Error "POOL_AUTOEXTEND_PERCENT not specified"
    return 1
  fi

  cat <<EOF > $tmpFile
activation {
	thin_pool_autoextend_threshold=${POOL_AUTOEXTEND_THRESHOLD}
	thin_pool_autoextend_percent=${POOL_AUTOEXTEND_PERCENT}

}
EOF
  mv -Z $tmpFile ${profileDir}/${profileFile}
  lvchange --metadataprofile ${profileName}  ${volume_group}/${pool_volume}
}

disable_auto_pool_extension() {
  local volume_group=$1
  local pool_volume=$2
  local profileName="${volume_group}--${pool_volume}-extend"
  local profileFile="${profileName}.profile"
  local profileDir

  profileDir=$(lvm dumpconfig --type full | grep "profile_dir" | cut -d "=" -f2 | sed 's/"//g')
  [ -n "$profileDir" ] || return 1

  lvchange --detachprofile ${volume_group}/${pool_volume}
  rm -f ${profileDir}/${profileFile}
}

process_auto_pool_extenion() {
  local vg=$1 thinpool_name=$2

  # Enable or disable automatic pool extension
  if [ "$AUTO_EXTEND_POOL" == "yes" ];then
    enable_auto_pool_extension ${vg} ${thinpool_name}
  else
    disable_auto_pool_extension ${vg} ${thinpool_name}
  fi
}

# Gets the current ${_STORAGE_OPTIONS}= string.
get_current_storage_options() {
  local options

  if [ ! -f "${_STORAGE_OUT_FILE}" ];then
    return 0
  fi

  if options=$(grep -e "^${_STORAGE_OPTIONS}=" ${_STORAGE_OUT_FILE} | sed "s/${_STORAGE_OPTIONS}=//" | sed 's/^ *//' | sed 's/^"//' | sed 's/"$//');then
    echo $options
    return 0
  fi

  return 1
}

is_valid_storage_driver() {
  local driver=$1 d

  # Empty driver is valid. That means user does not want us to setup any
  # storage.
  [ -z "$driver" ] && return 0

  for d in $_STORAGE_DRIVERS;do
    [ "$driver" == "$d" ] && return 0
  done

  return 1
}

# Gets the existing storage driver configured in /etc/sysconfig/docker-storage
get_existing_storage_driver() {
  local options driver

  options=$_CURRENT_STORAGE_OPTIONS

  [ -z "$options" ] && return 0

  # Check if -storage-driver <driver> is there.
  if ! driver=$(echo $options | sed -n 's/.*\(--storage-driver [ ]*[a-z0-9]*\).*/\1/p' | sed 's/--storage-driver *//');then
    return 1
  fi

  # If pattern does not match then driver == options.
  if [ -n "$driver" ] && [ ! "$driver" == "$options" ];then
    echo $driver
    return 0
  fi

  # Check if -s <driver> is there.
  if ! driver=$(echo $options | sed -n 's/.*\(-s [ ]*[a-z][0-9]*\).*/\1/p' | sed 's/-s *//');then
    return 1
  fi

  # If pattern does not match then driver == options.
  if [ -n "$driver" ] && [ ! "$driver" == "$options" ];then
    echo $driver
    return 0
  fi

  # We shipped some versions where we did not specify -s devicemapper.
  # If dm.thinpooldev= is present driver is devicemapper.
  if echo $options | grep -q -e "--storage-opt dm.thinpooldev=";then
    echo "devicemapper"
    return 0
  fi

  #Failed to determine existing storage driver.
  return 1
}

extra_volume_exists() {
  local lv_name=$1
  local vg=$2

  lvs $vg/$lv_name > /dev/null 2>&1 && return 0
  return 1
}

# This returns the mountpoint of $1
extra_lv_mountpoint() {
  local mounts
  local vg=$1
  local lv_name=$2
  local mount_dir=$3
  mounts=$(findmnt -n -o TARGET --source /dev/$vg/$lv_name | grep "^$mount_dir")
  echo $mounts
}

mount_extra_volume() {
  local vg=$1
  local lv_name=$2
  local mount_dir=$3
  remove_systemd_mount_target $mount_dir
  mounts=$(extra_lv_mountpoint $vg $lv_name $mount_dir)
  if [ -z "$mounts" ]; then
      mount -o pquota /dev/$vg/$lv_name $mount_dir
  fi
}

# Create a logical volume of size specified by first argument. Name of the
# volume is specified using second argument.
create_lv() {
  local data_size=$1
  local data_lv_name=$2

  # TODO: Error handling when data_size > available space.
  if [[ $data_size == *%* ]]; then
    lvcreate -y -l $data_size -n $data_lv_name $VG || return 1
  else
    lvcreate -y -L $data_size -n $data_lv_name $VG || return 1
  fi
return 0
}

setup_extra_volume() {
  local lv_name=$1
  local mount_dir=$2
  local lv_size=$3

  if ! create_lv $lv_size $lv_name; then
    Fatal "Failed to create volume $lv_name of size ${lv_size}."
  fi

  if ! mkfs -t xfs /dev/$VG/$lv_name > /dev/null; then
    Fatal "Failed to create filesystem on /dev/$VG/${lv_name}."
  fi

  if ! mount_extra_volume $VG $lv_name $mount_dir; then
    Fatal "Failed to mount volume ${lv_name} on ${mount_dir}"
  fi

  # setup right selinux label first time fs is created. Mount operation
  # changes the label of directory to reflect the label on root inode
  # of mounted fs.
  if ! restore_selinux_context $mount_dir; then
    return 1
  fi
}

# This is used only in compatibility mode. We are still using systemd
# mount unit only for compatibility mode. Reason being that upon service
# restart, we run into races and device might not yet be up when we try
# to mount it. And we don't know if this is first time start and we need
# to create volume or this is restart and we need to wait for device. So
# continue to use systemd mount unit for compatibility mode.
setup_systemd_mount_unit_compat() {
  local filename
  local vg=$1
  local lv_name=$2
  local mount_dir=$3
  local unit_file_path

  # filename must match the path ${mount_dir}.
  # e.g if ${mount_dir} is /var/lib/containers
  # then filename will be var-lib-containers.mount
  filename=$(systemd_escaped_filename ${mount_dir})
  unit_file_path="/etc/systemd/system/$filename"

  # If unit file already exists, nothing to do.
  [ -f "$unit_file_path" ] && return 0

  cat <<EOF > "${unit_file_path}.tmp"
# WARNING: This file was auto generated by container-storage-setup. Do not
# edit it. In the future, this file might be moved to a different location.
[Unit]
Description=Mount $lv_name on $mount_dir directory.
Before=docker-storage-setup.service

[Mount]
What=/dev/$vg/$lv_name
Where=${mount_dir}
Type=xfs
Options=pquota

[Install]
WantedBy=docker-storage-setup.service
EOF
  mv "${unit_file_path}.tmp" "$unit_file_path"
  systemctl daemon-reload
  systemctl enable $filename >/dev/null 2>&1
  systemctl start $filename
}

setup_extra_lv_fs_compat() {
  [ -z "$_RESOLVED_MOUNT_DIR_PATH" ] && return 0
  if ! setup_extra_dir $_RESOLVED_MOUNT_DIR_PATH; then
    return 1
  fi
  # If we are restarting, then extra volume should exist. This unit
  # file is dependent on extra volume mount unit file. That means this
  # code should run after mount unit has activated successfully. That
  # means after extra volume has come up.

  # We had got rid of this logic and reintroducing it back. That means
  # there can be configurations out there which have extra volume but
  # don't have unit file. So in such case, drop a unit file now. This
  # is still racy though. There is no guarantee that volume will be
  # up by the time this code runs when unit file is not present already.
  if extra_volume_exists $CONTAINER_ROOT_LV_NAME $VG; then
    if ! setup_systemd_mount_unit_compat "$VG" "$CONTAINER_ROOT_LV_NAME" "$_RESOLVED_MOUNT_DIR_PATH"; then
      Fatal "Failed to setup systemd mount unit for extra volume $CONTAINER_ROOT_LV_NAME."
    fi
    return 0
  fi

  if [ -z "$CONTAINER_ROOT_LV_SIZE" ]; then
    Fatal "Specify a valid value for CONTAINER_ROOT_LV_SIZE."
  fi
  if ! check_data_size_syntax $CONTAINER_ROOT_LV_SIZE; then
    Fatal "CONTAINER_ROOT_LV_SIZE value $CONTAINER_ROOT_LV_SIZE is invalid."
  fi
  # Container runtime extra volume does not exist. Create one.
  if ! setup_extra_volume $CONTAINER_ROOT_LV_NAME $_RESOLVED_MOUNT_DIR_PATH $CONTAINER_ROOT_LV_SIZE; then
    Fatal "Failed to setup extra volume $CONTAINER_ROOT_LV_NAME."
  fi

  if ! setup_systemd_mount_unit_compat "$VG" "$CONTAINER_ROOT_LV_NAME" "$_RESOLVED_MOUNT_DIR_PATH"; then
    Fatal "Failed to setup systemd mount unit for extra volume $CONTAINER_ROOT_LV_NAME."
  fi
}

setup_extra_lv_fs() {
  [ -z "$_RESOLVED_MOUNT_DIR_PATH" ] && return 0
  if ! setup_extra_dir $_RESOLVED_MOUNT_DIR_PATH; then
    return 1
  fi
  if extra_volume_exists $CONTAINER_ROOT_LV_NAME $VG; then
    if ! mount_extra_volume $VG $CONTAINER_ROOT_LV_NAME $_RESOLVED_MOUNT_DIR_PATH; then
      Fatal "Failed to mount volume $CONTAINER_ROOT_LV_NAME on $_RESOLVED_MOUNT_DIR_PATH"
    fi
    return 0
  fi
  if [ -z "$CONTAINER_ROOT_LV_SIZE" ]; then
    Fatal "Specify a valid value for CONTAINER_ROOT_LV_SIZE."
  fi
  if ! check_data_size_syntax $CONTAINER_ROOT_LV_SIZE; then
    Fatal "CONTAINER_ROOT_LV_SIZE value $CONTAINER_ROOT_LV_SIZE is invalid."
  fi
  # Container runtime extra volume does not exist. Create one.
  if ! setup_extra_volume $CONTAINER_ROOT_LV_NAME $_RESOLVED_MOUNT_DIR_PATH $CONTAINER_ROOT_LV_SIZE; then
    Fatal "Failed to setup extra volume $CONTAINER_ROOT_LV_NAME."
  fi
}

setup_docker_root_lv_fs() {
  [ "$DOCKER_ROOT_VOLUME" != "yes" ] && return 0
  if ! setup_docker_root_dir; then
    return 1
  fi
  if extra_volume_exists $_DOCKER_ROOT_LV_NAME $VG; then
    if ! mount_extra_volume $VG $_DOCKER_ROOT_LV_NAME $_DOCKER_ROOT_DIR; then
      Fatal "Failed to mount volume $_DOCKER_ROOT_LV_NAME on $_DOCKER_ROOT_DIR"
    fi
    return 0
  fi
  if [ -z "$DOCKER_ROOT_VOLUME_SIZE" ]; then
    Fatal "Specify a valid value for DOCKER_ROOT_VOLUME_SIZE."
  fi
  if ! check_data_size_syntax $DOCKER_ROOT_VOLUME_SIZE; then
    Fatal "DOCKER_ROOT_VOLUME_SIZE value $DOCKER_ROOT_VOLUME_SIZE is invalid."
  fi
  # Docker root volume does not exist. Create one.
  if ! setup_extra_volume $_DOCKER_ROOT_LV_NAME $_DOCKER_ROOT_DIR $DOCKER_ROOT_VOLUME_SIZE; then
    Fatal "Failed to setup logical volume $_DOCKER_ROOT_LV_NAME."
  fi
}

check_storage_options(){
  if [ "$STORAGE_DRIVER" == "devicemapper" ] && [ -z "$CONTAINER_THINPOOL" ];then
     Fatal "CONTAINER_THINPOOL must be defined for the devicemapper storage driver."
  fi

  # Populate $_RESOLVED_MOUNT_DIR_PATH
  if [ -n "$CONTAINER_ROOT_LV_MOUNT_PATH" ];then
    if ! _RESOLVED_MOUNT_DIR_PATH=$(realpath $CONTAINER_ROOT_LV_MOUNT_PATH);then
	Fatal "Failed to resolve path $CONTAINER_ROOT_LV_MOUNT_PATH"
    fi
  fi

  if [ "$DOCKER_ROOT_VOLUME" == "yes" ] && [ -n "$CONTAINER_ROOT_LV_MOUNT_PATH" ];then
     Fatal "DOCKER_ROOT_VOLUME and CONTAINER_ROOT_LV_MOUNT_PATH are mutually exclusive options."
  fi

  if [ -n "$CONTAINER_ROOT_LV_NAME" ] && [ -z "$CONTAINER_ROOT_LV_MOUNT_PATH" ];then
     Fatal "CONTAINER_ROOT_LV_MOUNT_PATH cannot be empty, when CONTAINER_ROOT_LV_NAME is set"
  fi

  if [ -n "$CONTAINER_ROOT_LV_MOUNT_PATH" ] && [ -z "$CONTAINER_ROOT_LV_NAME" ];then
     Fatal "CONTAINER_ROOT_LV_NAME cannot be empty, when CONTAINER_ROOT_LV_MOUNT_PATH is set"
  fi

  # Allow using DOCKER_ROOT_VOLUME only in compatibility mode.
  if [ "$DOCKER_ROOT_VOLUME" == "yes" ] && [ "$_DOCKER_COMPAT_MODE" != "1" ];then
     Fatal "DOCKER_ROOT_VOLUME is deprecated. Use CONTAINER_ROOT_LV_MOUNT_PATH instead."
  fi

  if [ "$DOCKER_ROOT_VOLUME" == "yes" ];then
     Info "DOCKER_ROOT_VOLUME is deprecated, and will be removed soon. Use CONTAINER_ROOT_LV_MOUNT_PATH instead."
  fi

  if [ -n "${EXTRA_DOCKER_STORAGE_OPTIONS}" ]; then
      Info "EXTRA_DOCKER_STORAGE_OPTIONS is deprecated, please use EXTRA_STORAGE_OPTIONS"
      if [ -n "${EXTRA_STORAGE_OPTIONS}" ]; then
	  Fatal "EXTRA_DOCKER_STORAGE_OPTIONS and EXTRA_STORAGE_OPTIONS are mutually exclusive options."
      fi
      EXTRA_STORAGE_OPTIONS=${EXTRA_DOCKER_STORAGE_OPTIONS}
      unset EXTRA_DOCKER_STORAGE_OPTIONS
  fi
}

# This is used in compatibility mode.
setup_storage_compat() {
  local current_driver
  local containerroot

  if [ "$STORAGE_DRIVER" == "" ];then
    Info "STORAGE_DRIVER not set, no storage will be configured. You must specify STORAGE_DRIVER if you want to configure storage."
    exit 0
  fi

  if ! is_valid_storage_driver $STORAGE_DRIVER;then
    Fatal "Invalid storage driver: ${STORAGE_DRIVER}."
  fi

  if ! current_driver=$(get_existing_storage_driver);then
    Fatal "Failed to determine existing storage driver."
  fi

  # If storage is configured and new driver should match old one.
  if [ -n "$current_driver" ] && [ "$current_driver" != "$STORAGE_DRIVER" ];then
   Info "Storage is already configured with ${current_driver} driver. Can't configure it with ${STORAGE_DRIVER} driver. To override, remove ${_STORAGE_OUT_FILE} and retry."
   check_existing_thinpool_compat || true
   return 0
  fi

  if [ "$STORAGE_DRIVER" == "overlay" -o "$STORAGE_DRIVER" == "overlay2" ]; then
    if ! can_mount_overlay; then
      Fatal "Can not setup storage driver $STORAGE_DRIVER as system does not support it. Specify a different driver."
    fi
  fi

  # If a user decides to setup (a) and (b)/(c):
  # a) lvm thin pool for devicemapper.
  # b) a separate volume for container runtime root.
  # c) a separate named ($CONTAINER_ROOT_LV_NAME) volume for $CONTAINER_ROOT_LV_MOUNT_PATH.
  # (a) will be setup first, followed by (b) or (c).

  # Set up lvm thin pool LV.
  if [ "$STORAGE_DRIVER" == "devicemapper" ]; then
    setup_lvm_thin_pool_compat
  else
    write_storage_config_file $STORAGE_DRIVER "$_STORAGE_OUT_FILE"
  fi

  # If container root is on a separate volume, setup that.
  if ! setup_docker_root_lv_fs; then
    Error "Failed to setup docker root volume."
    return 1
  fi

  # Set up a separate named ($CONTAINER_ROOT_LV_NAME) volume
  # for $CONTAINER_ROOT_LV_MOUNT_PATH.
  if ! setup_extra_lv_fs_compat; then
    Error "Failed to setup logical volume for $CONTAINER_ROOT_LV_MOUNT_PATH."
    return 1
  fi

  if [ "$STORAGE_DRIVER" == "overlay" -o "$STORAGE_DRIVER" == "overlay2" ]; then
    # This is little hacky. We are guessing where overlay2 will be setup
    # by container runtime environment. At some point of time this should
    # be passed in by a config variable.
    containerroot=${_RESOLVED_MOUNT_DIR_PATH:-/var}

    if ! is_xfs_ftype_enabled "$containerroot"; then
       Error "XFS filesystem at ${containerroot} has ftype=0, cannot use overlay backend; consider different driver or separate volume or OS reprovision"
       return 1
    fi
  fi
}

restore_selinux_context() {
  local dir=$1

  if ! restorecon -R $dir; then
    Error "restorecon -R $dir failed."
    return 1
  fi
}

get_docker_root_dir(){
    local flag=false path
    options=$(grep -e "^OPTIONS" /etc/sysconfig/docker|cut -d"'" -f 2)
    for opt in $options
    do
        if [ "$flag" = true ];then
           path=$opt
           flag=false
           continue
        fi
	case "$opt" in
            "-g"|"--graph")
                flag=true
                ;;
            -g=*|--graph=*)
		path=$(echo $opt|cut -d"=" -f 2)
                ;;
            *)
                ;;
        esac
    done
    if [ -z "$path" ];then
      return
    fi
    if ! _DOCKER_ROOT_DIR=$(realpath -m $path);then
      Fatal "Failed to resolve path $path"
    fi
}

setup_extra_dir() {
  local resolved_mount_dir_path=$1
  [ -d "$resolved_mount_dir_path" ] && return 0

  # Directory does not exist. Create one.
  mkdir -p $resolved_mount_dir_path
  return $?
}

setup_docker_root_dir() {
  if ! get_docker_root_dir; then
    return 1
  fi

  [ -d "_$DOCKER_ROOT_DIR" ] && return 0

  # Directory does not exist. Create one.
  mkdir -p $_DOCKER_ROOT_DIR
  return $?
}


# This deals with determining rootfs, root vg and pvs etc and sets the
# global variables accordingly.
determine_rootfs_pvs_vg() {
  # Read mounts
  _ROOT_DEV=$( awk '$2 ~ /^\/$/ && $1 !~ /rootfs/ { print $1 }' /proc/mounts )
  if ! _ROOT_VG=$(lvs --noheadings -o vg_name $_ROOT_DEV 2>/dev/null);then
    Info "Volume group backing root filesystem could not be determined"
    _ROOT_VG=
  else
    _ROOT_VG=$(echo $_ROOT_VG | sed -e 's/^ *//' -e 's/ *$//')
  fi

  _ROOT_PVS=
  if [ -n "$_ROOT_VG" ];then
    _ROOT_PVS=$( pvs --noheadings -o pv_name,vg_name | awk "\$2 ~ /^$_ROOT_VG\$/ { print \$1 }" )
  fi

  _VG_EXISTS=
  if [ -z "$VG" ]; then
    if [ -n "$_ROOT_VG" ]; then
      VG=$_ROOT_VG
      _VG_EXISTS=1
    fi
  else
    if vg_exists "$VG";then
      _VG_EXISTS=1
    fi
  fi
}

partition_disks_create_vg() {
  local dev_list

  # If there is no volume group specified or no root volume group, there is
  # nothing to do in terms of dealing with disks.
  if [[ -n "$DEVS" && -n "$VG" ]]; then
    _DEVS_RESOLVED=$(canonicalize_block_devs "${DEVS}") || return 1

    # If all the disks have already been correctly partitioned, there is
    # nothing more to do
    dev_list=$(scan_disks "$_DEVS_RESOLVED" "$VG" "$WIPE_SIGNATURES") || return 1
    if [[ -n "$dev_list" ]]; then
      for dev in $dev_list; do
        check_wipe_block_dev_sig $dev
      done
      create_disk_partitions "$dev_list"
      create_extend_volume_group
    fi
  fi
}

# This is used in compatibility mode.
reset_storage_compat() {
  local tpool

  # Check if a thin pool is already configured in /etc/sysconfig/docker-storage
  tpool=`get_configured_thin_pool`
  if [ -n "$_RESOLVED_MOUNT_DIR_PATH" ] && [ -n "$CONTAINER_ROOT_LV_NAME" ];then
    reset_extra_volume_compat $CONTAINER_ROOT_LV_NAME $_RESOLVED_MOUNT_DIR_PATH $VG
  fi

  if [ "$DOCKER_ROOT_VOLUME" == "yes" ];then
    if ! get_docker_root_dir; then
      return 1
    fi
    reset_extra_volume_compat $_DOCKER_ROOT_LV_NAME $_DOCKER_ROOT_DIR $VG
  fi

  if [ -n "$tpool" ]; then
    local tpool_vg tpool_lv
    Info "Found an already configured thin pool $tpool in ${_STORAGE_OUT_FILE}"
    if ! is_managed_tpool_compat "$tpool"; then
      Fatal "Thin pool ${tpool} does not seem to be managed by container-storage-setup. Exiting."
    fi
    tpool_vg=`get_dmdev_vg $tpool`
    reset_lvm_thin_pool ${CONTAINER_THINPOOL} "$tpool_vg"
  elif [ "$STORAGE_DRIVER" == "devicemapper" ]; then
    reset_lvm_thin_pool ${CONTAINER_THINPOOL} $VG
  fi

  rm -f ${_STORAGE_OUT_FILE}
}

usage() {
  cat <<-FOE
    Usage: $1 [OPTIONS]
    Usage: $1 [OPTIONS] COMMAND [arg...]

    Grows the root filesystem and sets up storage for container runtimes

    Options:
      --help    Print help message
      --reset   Reset your docker storage to init state. 
      --version Print version information.

    Commands:
      create		Create storage configuration
      activate		Activate storage configuration
      deactivate	Deactivate storage configuration
      remove		Remove storage configuration
      list		List storage configuration
      export		Send storage configuration output file to stdout
      add-dev		Add block device to storage configuration
FOE
}

#
# START of Helper functions dealing with commands and storage setup for new
# design
#
# Functions dealing with metadata handling
create_metadata() {
  local metafile=$1

  cat > ${metafile}.tmp <<-EOF
	_M_METADATA_VERSION=$_METADATA_VERSION
	_M_STORAGE_DRIVER=$STORAGE_DRIVER
	_M_VG=$VG
	_M_VG_CREATED=$_VG_CREATED
	_M_DEVS_RESOLVED="$_DEVS_RESOLVED"
	_M_CONTAINER_THINPOOL=$CONTAINER_THINPOOL
	_M_CONTAINER_ROOT_LV_NAME=$CONTAINER_ROOT_LV_NAME
	_M_CONTAINER_ROOT_LV_MOUNT_PATH=$CONTAINER_ROOT_LV_MOUNT_PATH
	_M_AUTO_EXTEND_POOL=$AUTO_EXTEND_POOL
	_M_DEVICE_WAIT_TIMEOUT=$DEVICE_WAIT_TIMEOUT
EOF
  mv ${metafile}.tmp ${metafile}
}

metadata_update_add_dev() {
  local metafile=$1
  local new_resolved_dev=$2
  local updated_resolved_devs

  cp $metafile ${metafile}.tmp

  if [ -z "$_M_DEVS_RESOLVED" ]; then
    updated_resolved_devs="$new_resolved_dev"
  else
    updated_resolved_devs="$_M_DEVS_RESOLVED $new_resolved_dev"
  fi

  if ! sed -i "s;^_M_DEVS_RESOLVED=.*$;_M_DEVS_RESOLVED=\"${updated_resolved_devs}\";" ${metafile}.tmp;then
    Error "Failed to update _M_DEVS_RESOLVED in metadata."
    return 1
  fi
  mv ${metafile}.tmp ${metafile}
}

set_config_status() {
  local config_name=$1
  local status=$2
  local status_file="$_CONFIG_DIR/$config_name/$_STATUSFILE_NAME"

  mkdir -p "$_CONFIG_DIR/$config_name"
  echo "$status" > ${status_file}.tmp
  mv ${status_file}.tmp ${status_file}
}

get_config_status() {
  local config_name=$1
  local status_file="$_CONFIG_DIR/$config_name/$_STATUSFILE_NAME"
  local curr_status

  curr_status=`cat $status_file`
  echo $curr_status
}

create_storage_config() {
  local config_path=$1
  local infile=$2

  mkdir -p $config_path
  cp $infile $config_path/$_INFILE_NAME
  touch $config_path/$_METAFILE_NAME

  create_metadata "$config_path/$_METAFILE_NAME"
  write_storage_config_file "$STORAGE_DRIVER" "$config_path/$_OUTFILE_NAME"
}

# activate command processing start

# Wait for thin pool for certain time interval. If thinpool is found 0 is
# returned otherwise 1.
wait_for_thinpool() {
  local thinpool_name=$1
  local vg=$2
  local timeout=$3

  if lvm_pool_exists $thinpool_name $vg; then
    return 0
  fi

  if [ -z "$timeout" ] || [ "$timeout" == "0" ];then
    return 1
  fi

  while [ $timeout -gt 0 ]; do
    Info "Waiting for lvm thin pool $vg/${thinpool_name}. Wait time remaining is $timeout seconds"
    if [ $timeout -le 5 ];then
      sleep $timeout
    else
      sleep 5
    fi
    timeout=$((timeout-5))
    if lvm_pool_exists $thinpool_name $vg; then
      return 0
    fi
  done

  Info "Timed out waiting for lvm thin pool $vg/${thinpool_name}"
  return 1
}

activate_devicemapper() {
  local thinpool_name=$1
  local vg=$2
  local timeout=$3

  # TODO: Add logic to activate volume group. For now it assumes that
  # volume group will auto activate when devices are ready.

  # Wait for thin pool
  if ! wait_for_thinpool $thinpool_name $vg $timeout;then
    return 1
  fi

  # Activate thin pool
  if ! lvchange -ay -K $vg/$thinpool_name; then
    Error "Thin pool $vg/$thinpool_name activation failed"
    return 1
  fi
  return 0
}

activate_storage_driver() {
  local driver=$1

  if ! is_valid_storage_driver $driver; then
    Error "Invalid storage driver $driver"
    return 1
  fi

  [ "$driver" == "" ] && return 0
  [ "$driver" == "overlay" -o "$driver" == "overlay2" ] && return 0

  if [ "$driver" == "devicemapper" ];then
    if ! activate_devicemapper $_M_CONTAINER_THINPOOL $_M_VG $_M_DEVICE_WAIT_TIMEOUT; then
      Error "Activation of driver $driver failed"
     return 1
    fi
  fi
}

# Wait for logical volume
wait_for_lv() {
  local lv_name=$1
  local vg=$2
  local timeout=$3

  if extra_volume_exists $lv_name $vg; then
    return 0
  fi

  if [ -z "$timeout" ] || [ "$timeout" == "0" ];then
    return 1
  fi

  while [ $timeout -gt 0 ]; do
    Info "Waiting for logical volume $vg/${lv_name}. Wait time remaining is $timeout seconds"
    if [ $timeout -le 5 ];then
      sleep $timeout
    else
      sleep 5
    fi
    timeout=$((timeout-5))
    if extra_volume_exists $lv_name $vg; then
      return 0
    fi
  done

  Info "Timed out waiting for logical volume $vg/${lv_name}"
  return 1
}

activate_extra_lv_fs() {
  local lv_name=$1
  local vg=$2
  local timeout=$3
  local mount_path=$4

  if ! wait_for_lv $lv_name $vg $timeout; then
    Error "logical volume $vg/${lv_name} does not exist"
    return 1
  fi

  if ! lvchange -ay $vg/$lv_name; then
      Error "Failed to activate volume $vg/$lv_name"
      return 1
  fi

  if ! mount_extra_volume $vg $lv_name $mount_path; then
      Error "Failed to mount volume $vg/$lv_name on $mount_path"
      return 1
  fi
}

# activate command processing start
run_command_activate() {
  local metafile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME"
  local status_file="$_CONFIG_DIR/$_CONFIG_NAME/$_STATUSFILE_NAME"
  local curr_status

  [ ! -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME does not exist"

  [ ! -e "$metafile_path" ] && Fatal "Storage configuration $_CONFIG_NAME metadata does not exist"
  source "$metafile_path"

  if ! curr_status=$(cat $status_file); then
    Fatal "Failed to determine current status of storage configuration $_CONFIG_NAME"
  fi

  if [ "$curr_status" == "invalid" ];then
    Fatal "Storage configuration $_CONFIG_NAME is invalid. Can't activate it."
  fi

  if ! activate_storage_driver $_M_STORAGE_DRIVER; then
    Fatal "Activation of storage config $_CONFIG_NAME failed"
  fi

  # Populate $_RESOLVED_MOUNT_DIR_PATH
  if [ -n "$_M_CONTAINER_ROOT_LV_MOUNT_PATH" ];then
    if ! _RESOLVED_MOUNT_DIR_PATH=$(realpath $_M_CONTAINER_ROOT_LV_MOUNT_PATH);then
      Fatal "Failed to resolve path $_M_CONTAINER_ROOT_LV_MOUNT_PATH"
    fi

    if ! activate_extra_lv_fs $_M_CONTAINER_ROOT_LV_NAME $_M_VG $_M_DEVICE_WAIT_TIMEOUT $_RESOLVED_MOUNT_DIR_PATH; then
      Fatal "Activation of storage config $_CONFIG_NAME failed"
    fi
  fi

  set_config_status "$_CONFIG_NAME" "active"
  echo "Activated storage config $_CONFIG_NAME"
}

activate_help() {
  cat <<-FOE
    Usage: $1 activate [OPTIONS] CONFIG_NAME

    Activate storage configuration specified by CONFIG_NAME

    Options:
      -h, --help	Print help message
FOE
}

process_command_activate() {
  local command="$1"
  local command_opts=`echo "$command" | sed 's/activate //'`

  parsed_opts=`getopt -o h -l help -- $command_opts`
  eval set -- "$parsed_opts"
  while true ; do
    case "$1" in
        -h | --help)  activate_help $(basename $0); exit 0;;
        --) shift; break;;
    esac
  done

  case $# in
    1)
       _CONFIG_NAME=$1
      ;;
    *)
      activate_help $(basename $0); exit 0;;
  esac
}
# activate command processing end

#
# deactivate command processing start
#
deactivate_devicemapper() {
  local thinpool_name=$1
  local vg=$2

  # Deactivate thin pool
  if ! lvchange -an $vg/$thinpool_name; then
    Error "Thin pool $vg/$thinpool_name deactivation failed"
    return 1
  fi
  return 0
}

deactivate_storage_driver() {
  local driver=$1

  if ! is_valid_storage_driver $driver; then
    Error "Invalid storage driver $driver"
    return 1
  fi

  [ "$driver" == "" ] && return 0
  [ "$driver" == "overlay" -o "$driver" == "overlay2" ] && return 0

  if [ "$driver" == "devicemapper" ];then
    if ! deactivate_devicemapper $_M_CONTAINER_THINPOOL $_M_VG; then
      Error "Deactivation of driver $driver failed"
     return 1
    fi
  fi
}

deactivate_extra_lv_fs() {
  local lv_name=$1
  local vg=$2
  local mount_path=$3

  if mountpoint -q $mount_path; then
    if ! umount $mount_path; then
      Error "Failed to unmount $mount_path"
      return 1
    fi
  fi

  #TODO: Most likely we will have to try deactivation in a loop to make
  # sure any udev rules have run and now lv is not busy.

  if ! lvchange -an $vg/$lv_name; then
    Error "Failed to deactivate $vg/$lv_name"
    return 1
  fi
}

run_command_deactivate() {
  local resolved_path
  local metafile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME"

  [ ! -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME does not exist"

  [ ! -e "$metafile_path" ] && Fatal "Storage configuration $_CONFIG_NAME metadata does not exist"
  source "$metafile_path"

  if ! deactivate_storage_driver $_M_STORAGE_DRIVER; then
	  Fatal "Deactivation of storage config $_CONFIG_NAME failed"
  fi

  if [ -n "$_M_CONTAINER_ROOT_LV_MOUNT_PATH" ];then
    if ! resolved_path=$(realpath $_M_CONTAINER_ROOT_LV_MOUNT_PATH);then
      Fatal "Failed to resolve path $_M_CONTAINER_ROOT_LV_MOUNT_PATH"
    fi

    if ! deactivate_extra_lv_fs $_M_CONTAINER_ROOT_LV_NAME $_M_VG $resolved_path; then
      Fatal "Deactivation of storage config $_CONFIG_NAME failed"
    fi
  fi

  set_config_status "$_CONFIG_NAME" "inactive"
  echo "Deactivated storage config $_CONFIG_NAME"
}

deactivate_help() {
  cat <<-FOE
    Usage: $1 deactivate [OPTIONS] CONFIG_NAME

    De-activate storage configuration specified by CONFIG_NAME

    Options:
      -h, --help	Print help message
FOE
}

process_command_deactivate() {
  local command="$1"
  local command_opts=`echo "$command" | sed 's/deactivate //'`

  parsed_opts=`getopt -o h -l help -- $command_opts`
  eval set -- "$parsed_opts"
  while true ; do
    case "$1" in
        -h | --help)  deactivate_help $(basename $0); exit 0;;
        --) shift; break;;
    esac
  done

  case $# in
    1)
       _CONFIG_NAME=$1
      ;;
    *)
      deactivate_help $(basename $0); exit 0;;
  esac
}

#
# deactivate command processing end
#

#
# Remove command processing start
#
reset_extra_volume() {
  local mp filename
  local lv_name=$1
  local mount_dir=$2
  local vg=$3

  if ! extra_volume_exists $lv_name $vg; then
     return 0
  fi

  mp=$(extra_lv_mountpoint $vg $lv_name $mount_dir)
  if [ -n "$mp" ];then
    if ! umount $mp >/dev/null 2>&1; then
      Fatal "Failed to unmount $mp"
    fi
  fi
  lvchange -an $vg/${lv_name}
  lvremove $vg/${lv_name}
}

# Remove command processing
reset_storage() {
  local resolved_path dev

  # Populate $_RESOLVED_MOUNT_DIR_PATH
  if [ -n "$_M_CONTAINER_ROOT_LV_MOUNT_PATH" ];then
    if ! resolved_path=$(realpath $_M_CONTAINER_ROOT_LV_MOUNT_PATH);then
      Error "Failed to resolve path $_M_CONTAINER_ROOT_LV_MOUNT_PATH"
      return 1
    fi

    if ! reset_extra_volume $_M_CONTAINER_ROOT_LV_NAME $resolved_path $_M_VG;then
      Error "Failed to remove volume $_M_CONTAINER_ROOT_LV_NAME"
      return 1
    fi
  fi

  if [ "$_M_STORAGE_DRIVER" == "devicemapper" ]; then
    if ! reset_lvm_thin_pool $_M_CONTAINER_THINPOOL $_M_VG; then
      Error "Failed to remove thinpool $_M_VG/$_M_CONTAINER_THINPOOL"
      return 1
    fi
  fi

  # If we created a volume group, remove volume group.
  if [ "$_M_VG_CREATED" == "1" ];then
    if ! remove_vg_if_exists "$_M_VG"; then
      Error "Failed to remove volume group $_M_VG"
      return 1
    fi

    # Cleanup any disks we added to volume group.
    if ! remove_disk_pvs_parts "$_M_DEVS_RESOLVED";then
      return 1
    fi
  fi

  # Get rid of config data
  rm -rf "$_CONFIG_DIR/$_CONFIG_NAME/"
}

run_command_remove() {
  local metafile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME"
  local curr_status

  [ ! -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME does not exist"

  # Source stored metadata file.
  [ ! -e "$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME metadata does not exist"

  source "$metafile_path"

  # If storage is active, deactivate it first
  curr_status=$(get_config_status "$_CONFIG_NAME")
  if [ "$curr_status" == "active" ];then
    if ! run_command_deactivate; then
       Fatal "Failed to remove storage config $_CONFIG_NAME"
    fi
  fi

  set_config_status "$_CONFIG_NAME" "invalid"
  if ! reset_storage; then
    Fatal "Failed to remove storage config $_CONFIG_NAME"
  fi

  echo "Removed storage configuration $_CONFIG_NAME"
}

remove_help() {
  cat <<-FOE
    Usage: $1 remove [OPTIONS] CONFIG_NAME

    Remove storage configuration specified by CONFIG_NAME

    Options:
      -h, --help	Print help message
FOE
}

process_command_remove() {
  local command="$1"
  local command_opts=`echo "$command" | sed 's/remove //'`

  parsed_opts=`getopt -o h -l help  -- $command_opts`
  eval set -- "$parsed_opts"
  while true ; do
    case "$1" in
        -h | --help)  remove_help $(basename $0); exit 0;;
        --) shift; break;;
    esac
  done

  case $# in
    1)
       _CONFIG_NAME=$1
      ;;
    *)
      remove_help $(basename $0); exit 0;;
  esac
}

#
# Remove command processing end
#

#
# list command processing start
#
list_all_configs() {
  local all_configs=$(ls "$_CONFIG_DIR" 2>/dev/null)
  local config_name storage_driver
  local status_file curr_status metadata_file

  [ -z "$all_configs" ] && return 0

  printf "%-24s %-16s %-16s\n" "NAME" "DRIVER" "STATUS"
  for config_name in $all_configs; do
    status_file="$_CONFIG_DIR/$config_name/$_STATUSFILE_NAME"
    metadata_file="$_CONFIG_DIR/$config_name/$_METAFILE_NAME"
    curr_status=`cat $status_file`
    storage_driver=`grep _M_STORAGE_DRIVER $metadata_file | cut -d "=" -f2`

    printf "%-24s %-16s %-16s\n" "$config_name" "$storage_driver" "$curr_status"
  done
}

#TODO: What should be listed in what format
list_overlay_params() {
  echo "VG=$_M_VG"
  echo "DEVS=$_M_DEVS_RESOLVED"
  echo "CONTAINER_ROOT_LV_NAME=$_M_CONTAINER_ROOT_LV_NAME"
  echo "CONTAINER_ROOT_LV_MOUNT_PATH=$_M_CONTAINER_ROOT_LV_MOUNT_PATH"
}

list_devicemapper_params() {
  echo "VG=$_M_VG"
  echo "DEVS=\"$_M_DEVS_RESOLVED\""
  echo "CONTAINER_THINPOOL=$_M_CONTAINER_THINPOOL"
  echo "CONTAINER_ROOT_LV_NAME=$_M_CONTAINER_ROOT_LV_NAME"
  echo "CONTAINER_ROOT_LV_MOUNT_PATH=$_M_CONTAINER_ROOT_LV_MOUNT_PATH"
  echo "AUTO_EXTEND_POOL=$_M_AUTO_EXTEND_POOL"
  echo "DEVICE_WAIT_TIMEOUT=$_M_DEVICE_WAIT_TIMEOUT"
}

list_config() {
  local config_name=$1
  local status_file curr_status

  status_file="$_CONFIG_DIR/$config_name/$_STATUSFILE_NAME"
  curr_status=`cat $status_file`

  echo "Name: $config_name"
  echo "Status: $curr_status"

  echo "STORAGE_DRIVER=$_M_STORAGE_DRIVER"

  if [ "$_M_STORAGE_DRIVER" == "" ]; then
    return 0
  elif [ "$_M_STORAGE_DRIVER" == "overlay" ]  || [ "$_M_STORAGE_DRIVER" == "overlay2" ];then
    list_overlay_params
  else
    list_devicemapper_params
  fi
  return 0
}

run_command_list() {
  if [ -z "$_CONFIG_NAME" ]; then
    list_all_configs
    return
  fi

  local metafile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME"

  [ ! -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME does not exist"

  # Source stored metadata file.
  [ ! -e "$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME metadata does not exist"

  source "$metafile_path"
  list_config "$_CONFIG_NAME"
}

list_help() {
  cat <<-FOE
    Usage: $1 list [OPTIONS] [CONFIG_NAME]

    List storage configuration

    Options:
      -h, --help	Print help message
FOE
}

process_command_list() {
  local command="$1"
  local command_opts=${command#"list"}

  parsed_opts=`getopt -o h -l help  -- $command_opts`
  eval set -- "$parsed_opts"
  while true ; do
    case "$1" in
        -h | --help)  list_help $(basename $0); exit 0;;
        --) shift; break;;
    esac
  done

  case $# in
    0)
      ;;
    1)
       _CONFIG_NAME=$1
      ;;
    *)
      list_help $(basename $0); exit 0;;
  esac
}

#
# list command processing end
#

#
# export command processing start
#
run_command_export() {
  local metafile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME"
  local outfile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_OUTFILE_NAME"

  [ ! -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME does not exist"

  [ ! -e "$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME metadata does not exist"

  [ ! -e "$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME output file does not exist"

  cat $outfile_path
}

export_help() {
  cat <<-FOE
    Usage: $1 export [OPTIONS] CONFIG_NAME

    Export storage configuration output file on stdout

    Options:
      -h, --help	Print help message
FOE
}

process_command_export() {
  local command="$1"
  local command_opts=${command#"export "}

  parsed_opts=`getopt -o h -l help  -- $command_opts`
  eval set -- "$parsed_opts"
  while true ; do
    case "$1" in
        -h | --help)  export_help $(basename $0); exit 0;;
        --) shift; break;;
    esac
  done

  case $# in
    1)
       _CONFIG_NAME=$1
      ;;
    *)
      export_help $(basename $0); exit 0;;
  esac
}

#
# export command processing end
#

#
# add-dev command processing start
#
run_command_add_dev() {
  local metafile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME"

  [ ! -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME does not exist"

  [ ! -e "$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME metadata does not exist"

  source $metafile_path

  [ -z "$_M_VG" ] && Fatal "No volume group is associated with configuration. Can not add disks."
  VG=$_M_VG

  if ! vg_exists "$VG";then
    Error "Volume group $VG does not exist."
    return 1
  fi

  _VG_EXISTS=1
  if ! partition_disks_create_vg; then
    Error "Failed to add device $DEVS to config $_CONFIG_NAME"
    return 1
  fi

  if ! metadata_update_add_dev $metafile_path "$DEVS"; then
    Error "Failed to add device $DEVS to config $_CONFIG_NAME"
    return 1
  fi

  echo "Added device $DEVS to storage configuration $_CONFIG_NAME"
}

add_dev_help() {
  cat <<-FOE
    Usage: $1 add-dev [OPTIONS] CONFIG_NAME DEVICE

    Add block device to configuration CONFIG_NAME

    Options:
      -h, --help	Print help message
FOE
}

process_command_add_dev() {
  local command="$1"
  local command_opts=${command#"add-dev "}

  parsed_opts=`getopt -o h -l help  -- $command_opts`
  eval set -- "$parsed_opts"
  while true ; do
    case "$1" in
        -h | --help)  add_dev_help $(basename $0); exit 0;;
        --) shift; break;;
    esac
  done

  case $# in
    2)
       _CONFIG_NAME=$1
       DEVS="$2"
      ;;
    *)
      add_dev_help $(basename $0); exit 0;;
  esac
}

#
# add-dev command processing end
#


#
# Start of create command processing
#
setup_lvm_thin_pool () {
  local thinpool_name=${CONTAINER_THINPOOL}

  # At this point of time, a volume group should exist for lvm thin pool
  # operations to succeed. Make that check and fail if that's not the case.
  if ! vg_exists "$VG";then
    Fatal "No valid volume group found. Exiting."
  fi
  _VG_EXISTS=1

  if lvm_pool_exists $thinpool_name; then
    Fatal "Thin pool named $thinpool_name already exists. Specify a different thin pool name."
  fi

  create_lvm_thin_pool

  # Mark thin pool for skip auto activation during reboot. start command
  # will activate thin pool.
  lvchange -ky $VG/$thinpool_name

  [ -n "$_STORAGE_OUT_FILE" ] &&  write_storage_config_file $STORAGE_DRIVER "$_STORAGE_OUT_FILE"

  process_auto_pool_extenion ${VG} ${thinpool_name}
}

setup_storage() {
  local containerroot

  if ! is_valid_storage_driver $STORAGE_DRIVER;then
    Fatal "Invalid storage driver: ${STORAGE_DRIVER}."
  fi

  if [ "$STORAGE_DRIVER" == "overlay" -o "$STORAGE_DRIVER" == "overlay2" ]; then
    if ! can_mount_overlay; then
      Fatal "Can not setup storage driver $STORAGE_DRIVER as system does not support it. Specify a different driver."
    fi
  fi

  # If a user decides to setup (a) and (b)/(c):
  # a) lvm thin pool for devicemapper.
  # b) a separate volume for container runtime root.
  # c) a separate named ($CONTAINER_ROOT_LV_NAME) volume for $CONTAINER_ROOT_LV_MOUNT_PATH.
  # (a) will be setup first, followed by (b) or (c).

  # Set up lvm thin pool LV.
  if [ "$STORAGE_DRIVER" == "devicemapper" ]; then
    setup_lvm_thin_pool
  elif [ "$STORAGE_DRIVER" == "overlay" -o "$STORAGE_DRIVER" == "overlay2" ];then
      [ -n "$_STORAGE_OUT_FILE" ] && write_storage_config_file $STORAGE_DRIVER "$_STORAGE_OUT_FILE"
  fi

  # Set up a separate named ($CONTAINER_ROOT_LV_NAME) volume
  # for $CONTAINER_ROOT_LV_MOUNT_PATH.
  if ! setup_extra_lv_fs; then
    Error "Failed to setup logical volume for $CONTAINER_ROOT_LV_MOUNT_PATH."
    return 1
  fi

  if [ "$STORAGE_DRIVER" == "overlay" -o "$STORAGE_DRIVER" == "overlay2" ]; then
    # This is little hacky. We are guessing where overlay2 will be setup
    # by container runtime environment. At some point of time this should
    # be passed in by a config variable.
    containerroot=${_RESOLVED_MOUNT_DIR_PATH:-/var}

    if ! is_xfs_ftype_enabled "$containerroot"; then
       Error "XFS filesystem at ${containerroot} has ftype=0, cannot use overlay backend; consider different driver or separate volume or OS reprovision"
       return 1
    fi
  fi
}

run_command_create() {
  # Verify storage options set correctly in input files
  [ -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME already exists"
  check_storage_options
  determine_rootfs_pvs_vg
  partition_disks_create_vg
  setup_storage
  create_storage_config "$_CONFIG_DIR/$_CONFIG_NAME" "$_STORAGE_IN_FILE"
  set_config_status "$_CONFIG_NAME" "active"
  echo "Created storage configuration $_CONFIG_NAME"
}

create_help() {
  cat <<-FOE
    Usage: $1 create [OPTIONS] CONFIG_NAME INPUTFILE

    Create storage configuration specified by CONFIG_NAME and INPUTFILE

    Options:
      -h, --help	Print help message
      -o, --output	Output file path
FOE
}

process_command_create() {
  local command="$1"
  local command_opts=`echo "$command" | sed 's/create //'`

  parsed_opts=`getopt -o ho: -l help,output:  -- $command_opts`
  eval set -- "$parsed_opts"
  while true ; do
    case "$1" in
        -h | --help)  create_help $(basename $0); exit 0;;
        -o | --output)  _STORAGE_OUT_FILE=$2; shift 2;;
        --) shift; break;;
    esac
  done

  case $# in
    2)
       _CONFIG_NAME=$1
       _STORAGE_IN_FILE=$2
       if [ ! -e "$_STORAGE_IN_FILE" ]; then
         Fatal "File $_STORAGE_IN_FILE does not exist."
       fi
      ;;
    *)
      create_help $(basename $0); exit 0;;
  esac
}

#
# End of create command processing
#
parse_subcommands() {
  local subcommand_str="$1"
  local subcommand=`echo "$subcommand_str" | cut -d " " -f1`

  case $subcommand in
    create)
      process_command_create "$subcommand_str"
      _COMMAND="create"
      ;;
    activate)
      process_command_activate "$subcommand_str"
      _COMMAND="activate"
      ;;
    deactivate)
      process_command_deactivate "$subcommand_str"
      _COMMAND="deactivate"
      ;;
    remove)
      process_command_remove "$subcommand_str"
      _COMMAND="remove"
      ;;
    list)
      process_command_list "$subcommand_str"
      _COMMAND="list"
      ;;
    export)
      process_command_export "$subcommand_str"
      _COMMAND="export"
      ;;
    add-dev)
      process_command_add_dev "$subcommand_str"
      _COMMAND="add-dev"
      ;;
    *)
      Error "Unknown command $subcommand"
      usage
      exit 1
      ;;
  esac
}

process_input_str() {
  local input="$1"
  local output

  # Look for commands and if one is found substitute with -- command so
  # that commands options are not parsed as css options by getopt

  for i in $_COMMAND_LIST; do
    if grep -w $i <<< "$input" > /dev/null 2>&1; then
      echo ${input/$i/-- $i}
      return
    fi
  done
  echo "$input"
}

#
# END of helper functions dealing with commands and storage setup for new design
#

#
# Start helper functions for locking
#
prepare_locking() {
  mkdir -p $_LOCKDIR
  eval "exec $_LOCKFD>"${_LOCKDIR}/$_LOCKFILE""
  # Supress lvm warnings about leaked file descriptor.
  export LVM_SUPPRESS_FD_WARNINGS=1
}

acquire_lock() {
  local timeout=60

  while [ $timeout -gt 0 ];do
    flock -n $_LOCKFD && return 0
    timeout=$((timeout-1))
    Info "Waiting to acquire lock ${_LOCKDIR}/$_LOCKFILE"
    sleep 1
  done

  Error "Timed out while waiting to acquire lock ${_LOCKDIR}/$_LOCKFILE"
  return 1
}


#
# End helper functions for locking
#
# Source library. If there is a library present in same dir as d-s-s, source
# that otherwise fall back to standard library. This is useful when modifyin
# libcss.sh in git tree and testing d-s-s.
_SRCDIR=`dirname $0`

if [ -e $_SRCDIR/libcss.sh ]; then
  source $_SRCDIR/libcss.sh
elif [ -e /usr/share/container-storage-setup/libcss.sh ]; then
  source /usr/share/container-storage-setup/libcss.sh
fi

if [ -e $_SRCDIR/container-storage-setup.conf ]; then
  source $_SRCDIR/container-storage-setup.conf
elif [ -e /usr/share/container-storage-setup/container-storage-setup ]; then
  source /usr/share/container-storage-setup/container-storage-setup
fi

# Main Script
_INPUT_STR="$@"
_INPUT_STR_MODIFIED=`process_input_str "$_INPUT_STR"`
_OPTS=`getopt -o hv -l reset -l help -l version -- $_INPUT_STR_MODIFIED`
eval set -- "$_OPTS"
_RESET=0
while true ; do
    case "$1" in
        --reset) _RESET=1; shift;;
        -h | --help)  usage $(basename $0); exit 0;;
        -v | --version)  echo $_CSS_VERSION; exit 0;;
        --) shift; break;;
    esac
done

# Check subcommands
case $# in
  0)
    CONTAINER_THINPOOL=docker-pool
    _DOCKER_COMPAT_MODE=1
    _STORAGE_IN_FILE="/etc/sysconfig/docker-storage-setup"
    _STORAGE_OUT_FILE="/etc/sysconfig/docker-storage"
    ;;
  *)
    _SUBCOMMAND_STR="$@"
    parse_subcommands "$_SUBCOMMAND_STR"
    ;;
esac

if [ -n "$_DOCKER_COMPAT_MODE" ]; then
   _STORAGE_OPTIONS="DOCKER_STORAGE_OPTIONS"
fi

# If user has overridden any settings in $_STORAGE_IN_FILE
# take that into account.
if [ -e "${_STORAGE_IN_FILE}" ]; then
  source ${_STORAGE_IN_FILE}
fi

# Take lock only in new mode and not compatibility mode
[ -z "$_DOCKER_COMPAT_MODE" ] && { prepare_locking; acquire_lock; }

case $_COMMAND in
  create)
    run_command_create
    ;;
  activate)
    run_command_activate
    ;;
  deactivate)
    run_command_deactivate
    ;;
  remove)
    run_command_remove
    ;;
  list)
    run_command_list
    ;;
  export)
    run_command_export
    ;;
  add-dev)
    run_command_add_dev
    ;;
  *)
    run_docker_compatibility_code
    ;;
esac
