#!/bin/bash

#
# lxc: linux Container library

# Authors:
# Daniel Lezcano <daniel.lezcano@free.fr>

# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.

# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.

# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

usage() {
    echo "usage: $(basename $0) -n NAME [-f CONFIG_FILE] [-t TEMPLATE] [FS_OPTIONS] --" >&2
    echo "         [TEMPLATE_OPTIONS]" >&2
    echo >&2
    echo "where FS_OPTIONS is one of:" >&2
    echo "  -B none" >&2
    echo "  -B lvm [--lvname LV_NAME] [--vgname VG_NAME] [--fstype FS_TYPE]" >&2
    echo "    [--fssize FS_SIZE]" >&2
    echo "  -B btrfs" >&2
}

help() {
    usage
    echo >&2
    echo "Create a new container on the system." >&2
    echo >&2
    echo "Options:" >&2
    echo "  -n NAME            specify the name of the container" >&2
    echo "  -f CONFIG_FILE     use an existing configuration file" >&2
    echo "  -t TEMPLATE        use an accessible template script" >&2
    echo "  -B BACKING_STORE   alter the container backing store (default: none)" >&2
    echo "  --lvname LV_NAME   specify the LVM logical volume name" >&2
    echo "                      (default: container name)" >&2
    echo "  --vgname VG_NAME   specify the LVM volume group name (default: lxc)" >&2
    echo "  --fstype FS_TYPE   specify the filesystem type (default: ext4)" >&2
    echo "  --fssize FS_SIZE   specify the filesystem size (default: 500M)" >&2
    echo >&2
    if [ -z $lxc_template ]; then
        echo "To see template-specific options, specify a template. For example:" >&2
        echo "  $(basename $0) -t ubuntu -h" >&2
        exit 0
    fi
    type ${templatedir}/lxc-$lxc_template 2>/dev/null
    if [ $? -eq 0 ]; then
        echo >&2
        echo "Template-specific options (TEMPLATE_OPTIONS):" >&2
        ${templatedir}/lxc-$lxc_template -h
    fi
}

shortoptions='hn:f:t:B:'
longoptions='help,name:,config:,template:,backingstore:,fstype:,lvname:,vgname:,fssize:'
lxc_path=/var/lib/lxc
bindir=/usr/bin
templatedir=/usr/share/lxc/templates
backingstore=_unset
fstype=ext4
fssize=500M
vgname=lxc

getopt=$(getopt -o $shortoptions --longoptions  $longoptions -- "$@")
if [ $? != 0 ]; then
    usage
    exit 1;
fi

eval set -- "$getopt"

while true; do
        case "$1" in
	    -h|--help)
		help
		exit 1
		;;
	    -n|--name)
		shift
		lxc_name=$1
		shift
		;;
	    -f|--config)
		shift
		lxc_config=$1
		shift
		;;
	    -t|--template)
		shift
		lxc_template=$1
		shift
		;;
	    -B|--backingstore)
		shift
		backingstore=$1
		shift
		;;
	    --lvname)
		shift
		lvname=$1
		shift
		;;
	    --vgname)
		shift
		vgname=$1
		shift
		;;
	    --fstype)
		shift
		fstype=$1
		shift
		;;
	    --fssize)
		shift
		fssize=$1
		shift
		;;
            --)
		shift
		break;;
            *)
		usage
		exit 1
		;;
        esac
done

# If -h or --help was passed into the container, we'll want to cleanup
# afterward
wantedhelp=0
for var in "$@"
do
if [ "$var" = "-h" -o "$var" = "--help" ]; then
    help
    exit 1
fi
done


if [ -z "$lxc_path" ]; then
    echo "$(basename $0): no configuration path defined" >&2
    exit 1
fi

if [ ! -r $lxc_path ]; then
    echo "$(basename $0): configuration path '$lxc_path' not found" >&2
    exit 1
fi

if [ -z "$lxc_name" ]; then
    echo "$(basename $0): no container name specified" >&2
    usage
    exit 1
fi

if [ -z "$lvname" ]; then
    lvname="$lxc_name"
fi

if [ "$(id -u)" != "0" ]; then
   echo "$(basename $0): must be run as root" >&2
   exit 1
fi

case "$backingstore" in
    lvm|none|btrfs|_unset) :;;
    *) echo "$(basename $0): '$backingstore' is not known (try 'none', 'lvm', 'btrfs')" >&2
        usage
        exit 1
        ;;
esac

if [ -d "$lxc_path/$lxc_name" ]; then
    echo "$(basename $0): '$lxc_name' already exists" >&2
    exit 1
fi

rootfs="$lxc_path/$lxc_name/rootfs"

if [ "$backingstore" = "_unset" -o "$backingstore" = "btrfs" ]; then
# if no backing store was given, then see if btrfs would work
    if which btrfs >/dev/null 2>&1 &&
        btrfs filesystem df "$lxc_path/" >/dev/null 2>&1; then
        backingstore="btrfs"
    else
        if [ "$backingstore" = "btrfs" ]; then
            echo "$(basename $0): missing 'btrfs' command or $lxc_path is not btrfs" >&2
            exit 1;
        fi
        backingstore="none"
    fi
fi

if [ $backingstore = "lvm" ]; then
    which vgscan > /dev/null
    if [ $? -ne 0 ]; then
        echo "$(basename $0): vgscan not found (is lvm2 installed?)" >&2
        exit 1
    fi
    grep -q "\<$fstype\>" /proc/filesystems
    if [ $? -ne 0 ]; then
        echo "$(basename $0): $fstype is not listed in /proc/filesystems" >&2
        exit 1
    fi

    vgscan | grep -q "Found volume group \"$vgname\""
    if [ $? -ne 0 ]; then
        echo "$(basename $0): could not find volume group \"$vgname\"" >&2
        exit 1
    fi

    rootdev=/dev/$vgname/$lvname
    lvdisplay $rootdev > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        echo "$(basename $0): backing store already exists: $rootdev" >&2
        echo "please delete it (using \"lvremove $rootdev\") and try again" >&2
        exit 1
    fi
elif [ "$backingstore" = "btrfs" ]; then
    mkdir "$lxc_path/$lxc_name"
    if ! out=$(btrfs subvolume create "$rootfs" 2>&1); then
        echo "$(basename $0): failed to create subvolume in $rootfs: $out" >&2
        exit 1;
    fi
fi

cleanup() {
    if [ $backingstore = "lvm" ]; then
        umount $rootfs
        lvremove -f $rootdev
    elif [ $backingstore = "btrfs" ]; then
        btrfs subvolume delete "$rootfs"
    fi
    ${bindir}/lxc-destroy -n $lxc_name
    echo "$(basename $0): aborted" >&2
    exit 1
}

trap cleanup HUP INT TERM

mkdir -p $lxc_path/$lxc_name

if [ -z "$lxc_config" ]; then
    touch $lxc_path/$lxc_name/config
else
    if [ ! -r "$lxc_config" ]; then
	echo "$(basename $0): '$lxc_config' configuration file not found" >&2
	exit 1
    fi

    cp $lxc_config $lxc_path/$lxc_name/config
fi

# Create the fs as needed
if [ $backingstore = "lvm" ]; then
    [ -d "$rootfs" ] || mkdir $rootfs
    lvcreate -L $fssize -n $lvname $vgname || exit 1
    udevadm settle
    mkfs -t $fstype $rootdev || exit 1
    mount -t $fstype $rootdev $rootfs
fi

if [ ! -z $lxc_template ]; then

    type ${templatedir}/lxc-$lxc_template 2>/dev/null
    if [ $? -ne 0 ]; then
        echo "$(basename $0): unknown template '$lxc_template'" >&2
        cleanup
    fi

    if [ -z "$lxc_config" ]; then
	echo "Note: Usually the template option is called with a configuration"
	echo "file option too, mostly to configure the network."
	echo "For more information look at lxc.conf (5)"
	echo
    fi

    ${templatedir}/lxc-$lxc_template --path=$lxc_path/$lxc_name --name=$lxc_name $*
    if [ $? -ne 0 ]; then
        echo "$(basename $0): failed to execute template '$lxc_template'" >&2
        cleanup
    fi

    echo "'$lxc_template' template installed"
fi

if [ $backingstore = "lvm" ]; then
    echo "Unmounting LVM"
    umount $rootfs

    # TODO: make the templates set this right from the start?
    sed -i '/lxc.rootfs/d' $lxc_path/$lxc_name/config
    echo "lxc.rootfs = $rootdev" >> $lxc_path/$lxc_name/config
fi

echo "'$lxc_name' created"
