#!/bin/bash

ORIGIN_IMG="origin.img"
ORIGIN_IMG_SIZE=2048
ORIGIN_LO_DEV="/dev/loop0"

COW_IMG="cow.img"
COW_IMG_SIZE=512
COW_LO_DEV="/dev/loop1"

ORIGIN_NAME="testorigin"
SNAPSHOT_NAME="testsnap"
SNAPSHOT_TYPE="P"
CHUNK_SIZE=8

READ_DELAY=0
WRITE_DELAY=1000

RWSPLIT_NAME="cow-rwsplit"

SECTOR_SIZE=512
SIZEOF_EXCEPTION_STRUCT=16
EXCEPTIONS_PER_AREA=$(($CHUNK_SIZE * $SECTOR_SIZE / $SIZEOF_EXCEPTION_STRUCT))

usage () {
    echo "usage: $(basename $0)" 2>&1
    exit 1
}

attach_lodev () {
    lodev="$1"
    img="$2"

    if losetup $lodev 2>/dev/null ; then
        echo "$lodev is already attached" 2>&1
        exit 1
    fi

    echo "Attaching $img to $lodev"
    losetup $lodev $img
}

create_loopbacks () {
    if [ -f $ORIGIN_IMG -o -f $COW_IMG ]; then
        echo "$ORIGIN_IMG or $COW_IMG already exists" 2>&1
        exit 1
    fi

    echo "Creating $ORIGIN_IMG (${ORIGIN_IMG_SIZE}M)"
    dd if=/dev/zero of=$ORIGIN_IMG bs=1M seek=$ORIGIN_IMG_SIZE count=0 2>/dev/null

    attach_lodev $ORIGIN_LO_DEV $ORIGIN_IMG

    echo "Creating a ext3 filesystem on $ORIGIN_LO_DEV"
    mke2fs -j $ORIGIN_LO_DEV

    echo "Creating $COW_IMG (${COW_IMG_SIZE}M)"
    dd if=/dev/zero of=$COW_IMG bs=1M seek=$COW_IMG_SIZE count=0 2>/dev/null

    attach_lodev $COW_LO_DEV $COW_IMG
}

remove_loopbacks () {
    echo "Detaching loopback devices"

    losetup -d $COW_LO_DEV

    if [ -f $COW_IMG ]; then
      echo "Removing $COW_IMG"
      rm -f $COW_IMG
    fi

    losetup -d $ORIGIN_LO_DEV

    if [ -f $ORIGIN_IMG ]; then
        echo "Removing $ORIGIN_IMG"
        rm -f $ORIGIN_IMG
    fi
}

create_rwsplit () {
    use_nbd=$1

    n_sectors=$(blockdev --getsize $COW_LO_DEV)

    split_tbl="0 $n_sectors rwsplit $COW_LO_DEV 0 $COW_LO_DEV 0 $READ_DELAY $WRITE_DELAY"

    echo "Setting up rwsplit device (/dev/mapper/$RWSPLIT_NAME)"
    echo $split_tbl | dmsetup create $RWSPLIT_NAME
}

remove_rwsplit () {
    echo "Remove rwsplit device (/dev/mapper/$RWSPLIT_NAME)"
    dmsetup remove $RWSPLIT_NAME
}

create_snapshot () {
    n_sectors=$(blockdev --getsize $ORIGIN_LO_DEV)

    cow_dev="/dev/mapper/$RWSPLIT_NAME"

    echo "Creating origin device (/dev/mapper/$ORIGIN_NAME)"
    echo "0 $n_sectors snapshot-origin $ORIGIN_LO_DEV" | dmsetup create $ORIGIN_NAME

    snapshot_tbl="0 $n_sectors snapshot $ORIGIN_LO_DEV $cow_dev $SNAPSHOT_TYPE $CHUNK_SIZE"

    echo "Creating snapshot of $ORIGIN_LO_DEV (/dev/mapper/$SNAPSHOT_NAME)"
    echo $snapshot_tbl | dmsetup create $SNAPSHOT_NAME
}

remove_snapshot () {
    echo "Removing snapshot device (/dev/mapper/$SNAPSHOT_NAME$s)"
    dmsetup remove $SNAPSHOT_NAME

    echo "Removing origin device (/dev/mapper/$ORIGIN_NAME)"
    dmsetup remove $ORIGIN_NAME
}

test_race_condition () {
    ss_dev="/dev/mapper/$SNAPSHOT_NAME"
    origin_dev="/dev/mapper/$ORIGIN_NAME"

    echo "Testing race condition on snapshot $ss_dev"

    n_sectors=$(blockdev --getsize $ORIGIN_LO_DEV)
    n_chunks=$(($n_sectors / $CHUNK_SIZE))

    echo "Filling second metadata area with ones"
    tmpfile=$(mktemp)
    dd if=/dev/zero bs=$(($SECTOR_SIZE * $CHUNK_SIZE)) count=1 2>/dev/null | tr '\0' '\377' > $tmpfile
    dd if=$tmpfile of=$COW_IMG seek=$((2 + $EXCEPTIONS_PER_AREA)) 2>/dev/null
    rm -f $tmpfile

    echo "Filling up the first metadata area with exceptions"
    dd if=/dev/zero of=$origin_dev bs=$(($SECTOR_SIZE * $CHUNK_SIZE)) count=$EXCEPTIONS_PER_AREA 2>/dev/null &

    echo "Suspending the /dev/mapper/$ORIGIN_NAME"
    dmsetup suspend $ORIGIN_NAME

    echo "Suspending the /dev/mapper/$SNAPSHOT_NAME"
    dmsetup suspend $SNAPSHOT_NAME

    echo "Reloading snapshot of $ORIGIN_LO_DEV (/dev/mapper/$SNAPSHOT_NAME)"
    cow_dev="/dev/mapper/$RWSPLIT_NAME"
    snapshot_tbl="0 $n_sectors snapshot $ORIGIN_LO_DEV $cow_dev $SNAPSHOT_TYPE $CHUNK_SIZE"
    echo $snapshot_tbl | dmsetup reload $SNAPSHOT_NAME

    wait
}


if [ $# -gt 0 ]; then
  usage
fi

create_loopbacks
create_rwsplit
create_snapshot

test_race_condition

remove_snapshot
remove_rwsplit
remove_loopbacks

