#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2015 Red Hat, Inc.  All Rights Reserved.
#
# common functions for setting up and tearing down a dmthin device

# SOOO many devices!
# Create the 2 pool devices on lvs so we can build the whole thing
# from a single scratch device

# Backing data dev
DMTHIN_DATA_NAME="thin-data.$seq"
DMTHIN_DATA_DEV="/dev/mapper/$DMTHIN_DATA_NAME"
# Backing metadata dev
DMTHIN_META_NAME="thin-meta.$seq"
DMTHIN_META_DEV="/dev/mapper/$DMTHIN_META_NAME"
# Backing pool dev (combination of above)
DMTHIN_POOL_NAME="thin-provision-pool.$seq"
DMTHIN_POOL_DEV="/dev/mapper/$DMTHIN_POOL_NAME"
# Thin volume
DMTHIN_VOL_NAME="thin-vol.$seq"
DMTHIN_VOL_DEV="/dev/mapper/$DMTHIN_VOL_NAME"

_dmthin_cleanup()
{
	_unmount $SCRATCH_MNT > /dev/null 2>&1
	_dmsetup_remove $DMTHIN_VOL_NAME
	_dmsetup_remove $DMTHIN_POOL_NAME
	_dmsetup_remove $DMTHIN_META_NAME
	_dmsetup_remove $DMTHIN_DATA_NAME
}

_dmthin_check_fs()
{
	_unmount $SCRATCH_MNT > /dev/null 2>&1
	_check_scratch_fs $DMTHIN_VOL_DEV
}

# Set up a dm-thin device on $SCRATCH_DEV
#
# All arguments are optional, and in this order; defaults follows:
# data_dev_size: half of $SCRATCH_DEV
# virtual_size: 10x $data_dev_size
# cluster_size: 512k
# low_water: 100M
#
# You may specify 0 to 4 of these arguments, but they
# must be in the above order.
_dmthin_init()
{
	local data_dev_size=$1	# Backing pool data size in sectors
	local virtual_size=$2	# Virtual size in sectors
	local cluster_size=$3   # cluster/alloc size, sectors
	local low_water=$4	# low water mark, sectors

	local dm_backing_dev=$SCRATCH_DEV
	local blk_dev_size=`blockdev --getsz $dm_backing_dev`

	local pool_id=$RANDOM

	# Default to something small-ish
	if [ -z "$data_dev_size" ]; then
		data_dev_size=$(($blk_dev_size / 2))
	fi

	# Default to something big-is; 10x backing
	if [ -z "$virtual_size" ]; then
		virtual_size=$(($data_dev_size * 10))
	fi

	# Default to 512k
	if [ -z "$cluster_size" ]; then
		cluster_size=1024	# 512k in sectors
	fi

	# Default to 100M
	if [ -z "$low_water" ]; then
		low_water=204800	# 100M, in sectors
	fi
	# low_water is expressed in blocks of size $cluster_size
	low_water=$((low_water / cluster_size))

	# Need to make linear metadata and data devs.  From kernel docs:
	# As a guide, we suggest you calculate the number of bytes to use in the
	# metadata device as 48 * $data_dev_size / $data_block_size but round it up
	# to 2MB (4096 sectors) if the answer is smaller.
	# So do that:

	local meta_dev_size=$((48 * $data_dev_size / $cluster_size))
	if [ "$meta_dev_size" -lt "4096" ]; then
		meta_dev_size=4096	# 2MB
	fi

	# scratch dev gets a metadata vol & data vol, start at this offset
	local meta_dev_offset=10240

	local total_data_dev_size=$(($meta_dev_offset + $meta_dev_size + $data_dev_size))
	if [ "$total_data_dev_size" -gt "$blk_dev_size" ]; then
		_notrun "$SCRATCH_DEV too small"
	fi

	# Unmount & tear down old stuff
	_dmthin_cleanup

	# Metadata device
	DMTHIN_META_TABLE="0 $meta_dev_size linear $dm_backing_dev $meta_dev_offset"
	_dmsetup_create $DMTHIN_META_NAME --table "$DMTHIN_META_TABLE" || \
		_fatal "failed to create dm thin meta device"

	# Data device
	local data_dev_offset=$((meta_dev_offset + $meta_dev_size))
	DMTHIN_DATA_TABLE="0 $data_dev_size linear $dm_backing_dev $data_dev_offset"
	_dmsetup_create $DMTHIN_DATA_NAME --table "$DMTHIN_DATA_TABLE" || \
		_fatal "failed to create dm thin data device"

	# Zap the pool metadata dev.  Explicitly fsync the zeroes to disk
	# because a slow-running udev running concurrently with dd can maintain
	# an open file descriptor.  The block layer only flushes the page cache
	# on last close, which means that the thin pool creation below will
	# see the (stale) ondisk contents and fail.
	dd if=/dev/zero of=$DMTHIN_META_DEV bs=4096 count=1 conv=fsync &>/dev/null

	# Thin pool
	# "start length thin-pool metadata_dev data_dev data_block_size low_water_mark"
	DMTHIN_POOL_TABLE="0 $data_dev_size thin-pool $DMTHIN_META_DEV $DMTHIN_DATA_DEV $cluster_size $low_water"
	_dmsetup_create $DMTHIN_POOL_NAME --table "$DMTHIN_POOL_TABLE" || \
		_fatal "failed to create dm thin pool device"

	# Thin volume
	$DMSETUP_PROG message $DMTHIN_POOL_DEV 0 "create_thin $pool_id" || \
		_fatal "failed to message pool device"

	# start length thin pool_dev dev_id [external_origin_dev]
	DMTHIN_VOL_TABLE="0 $virtual_size thin $DMTHIN_POOL_DEV $pool_id"
	_dmsetup_create $DMTHIN_VOL_NAME --table "$DMTHIN_VOL_TABLE" || \
		_fatal "failed to create dm thin volume device"

}

# for internal use
_dmthin_reload_table()
{
	local dev_name=$1
	local table="$2"

	$DMSETUP_PROG suspend $dev_name || \
		_fail  "dmsetup suspend of $dev_name failed"

	$DMSETUP_PROG load $dev_name --table "$table" || \
		_fail "dmsetup failed to reload $dev_name table"

	$DMSETUP_PROG resume $dev_name || \
		_fail  "dmsetup resume of $dev_name failed"

}

# Grow the dm-thin device by the given amount
# Argument is number of sectors to add, if not specified
# defaults to 1/4 of the $SCRATCH_DEV size
_dmthin_grow()
{
	local add_sectors=$1	# Number of sectors to add

	local dm_backing_dev=$SCRATCH_DEV
	local blk_dev_size=`blockdev --getsz $dm_backing_dev`

	# Get current sizes & values
	local   meta_dev_size=`$DMSETUP_PROG table | grep ^$DMTHIN_META_NAME | awk '{print $3}'`
	local meta_dev_offset=`$DMSETUP_PROG table | grep ^$DMTHIN_META_NAME | awk '{print $6}'`
	local   data_dev_size=`$DMSETUP_PROG table | grep ^$DMTHIN_DATA_NAME | awk '{print $3}'`
	local   pool_dev_size=`$DMSETUP_PROG table | grep ^$DMTHIN_POOL_NAME | awk '{print $3}'`
	local    cluster_size=`$DMSETUP_PROG table | grep ^$DMTHIN_POOL_NAME | awk '{print $7}'`
	local       low_water=`$DMSETUP_PROG table | grep ^$DMTHIN_POOL_NAME | awk '{print $8}'`

	# default to 25% growth
	if [ -z "$add_sectors" ]; then
		add_sectors=$(($data_dev_size / 4))
	fi

	local data_dev_offset=$(($meta_dev_offset + $meta_dev_size))

	# Figure new sizes
	data_dev_size=$(($data_dev_size + $add_sectors))
	pool_dev_size=$(($pool_dev_size + $add_sectors))

	# Can we do this?
	local total_data_dev_size=$(($meta_dev_offset + $meta_dev_size + $data_dev_size))
	if [ "$total_data_dev_size" -gt "$blk_dev_size" ]; then
		_fail "$SCRATCH_DEV too small"
	fi

	# Grow the data device
	DMTHIN_DATA_TABLE="0 $data_dev_size linear $dm_backing_dev $data_dev_offset"
	_dmthin_reload_table $DMTHIN_DATA_NAME "$DMTHIN_DATA_TABLE"

	# Grow the pool
	DMTHIN_POOL_TABLE="0 $data_dev_size thin-pool $DMTHIN_META_DEV $DMTHIN_DATA_DEV $cluster_size $low_water"
	_dmthin_reload_table $DMTHIN_POOL_NAME "$DMTHIN_POOL_TABLE"
}

# Queue IOs when full
_dmthin_set_queue()
{
	local   data_dev_size=`$DMSETUP_PROG table | grep $DMTHIN_DATA_NAME | awk '{print $3}'`
	local    cluster_size=`$DMSETUP_PROG table | grep $DMTHIN_POOL_NAME | awk '{print $7}'`
	local       low_water=`$DMSETUP_PROG table | grep $DMTHIN_POOL_NAME | awk '{print $8}'`

	DMTHIN_POOL_TABLE="0 $data_dev_size thin-pool $DMTHIN_META_DEV $DMTHIN_DATA_DEV $cluster_size $low_water"
	_dmthin_reload_table $DMTHIN_POOL_NAME "$DMTHIN_POOL_TABLE"
}

# Fail IOs when full
_dmthin_set_fail()
{
	local   data_dev_size=`$DMSETUP_PROG table | grep $DMTHIN_DATA_NAME | awk '{print $3}'`
	local    cluster_size=`$DMSETUP_PROG table | grep $DMTHIN_POOL_NAME | awk '{print $7}'`
	local       low_water=`$DMSETUP_PROG table | grep $DMTHIN_POOL_NAME | awk '{print $8}'`

	DMTHIN_POOL_TABLE="0 $data_dev_size thin-pool $DMTHIN_META_DEV $DMTHIN_DATA_DEV $cluster_size $low_water 1 error_if_no_space"
	_dmthin_reload_table $DMTHIN_POOL_NAME "$DMTHIN_POOL_TABLE"
}

_dmthin_mount_options()
{
	_scratch_options mount
	echo `_common_dev_mount_options $*` $SCRATCH_OPTIONS $DMTHIN_VOL_DEV $SCRATCH_MNT
}

_dmthin_mount()
{
	_mount -t $FSTYP `_dmthin_mount_options $*`
}

_dmthin_mkfs()
{
	_scratch_options mkfs
	_mkfs_dev $SCRATCH_OPTIONS "$@" $DMTHIN_VOL_DEV
}
_dmthin_try_mkfs()
{
	_scratch_options mkfs
	_try_mkfs_dev $SCRATCH_OPTIONS "$@" $DMTHIN_VOL_DEV
}
