#!/bin/sh

#
# 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

. /usr/share/lxc/lxc.functions

usage() {
    echo "usage: $(basename $0) -n NAME [-f CONFIG_FILE] [-t TEMPLATE] [FS_OPTIONS] --" >&2
    echo "         [-P lxcpath] [TEMPLATE_OPTIONS]" >&2
    echo >&2
    echo "where FS_OPTIONS is one of:" >&2
    echo "  -B none" >&2
    echo "  -B dir [--dir rootfs_dir]" >&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 "  --lxcpath path     specify an alternate container patch (default: $lxc_path)" >&2
    echo "  --lvname LV_NAME   specify the LVM logical volume name" >&2
    echo "                      (default: container name)" >&2
    echo "  --dir ROOTFS_DIR   specify path for custom rootfs directory location" >&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
    if [ -x ${templatedir}/lxc-$lxc_template ]; then
        echo >&2
        echo "Template-specific options (TEMPLATE_OPTIONS):" >&2
        ${templatedir}/lxc-$lxc_template -h
    fi
}

usage_err() {
    [ -n "$1" ] && echo "$1" >&2
    usage
    exit 1
}

optarg_check() {
    if [ -z "$2" ]; then
        usage_err "option '$1' requires an argument"
    fi
}

backingstore=_unset
fstype=ext4
fssize=500M
vgname=lxc
custom_rootfs=""

while [ $# -gt 0 ]; do
    opt="$1"
    shift
    case "$opt" in
        -h|--help)
            help
            exit 1
            ;;
        -n|--name)
            optarg_check $opt "$1"
            lxc_name=$1
            shift
            ;;
        -f|--config)
            optarg_check $opt "$1"
            lxc_config=$1
            shift
            ;;
        -P|--lxcpath)
            optarg_check $opt "$1"
            lxc_path=$1
            shift
            ;;
        -t|--template)
            optarg_check $opt "$1"
            lxc_template=$1
            shift
            ;;
        -B|--backingstore)
            optarg_check $opt "$1"
            backingstore=$1
            shift
            ;;
        --dir)
            optarg_check $opt "$1"
            custom_rootfs=$1
            shift
            ;;
        --lvname)
            optarg_check $opt "$1"
            lvname=$1
            shift
            ;;
        --vgname)
            optarg_check $opt "$1"
            vgname=$1
            shift
            ;;
        --fstype)
            optarg_check $opt "$1"
            fstype=$1
            shift
            ;;
        --fssize)
            optarg_check $opt "$1"
            fssize=$1
            shift
            ;;
        --)
            break;;
        -?)
            usage_err "unknown option '$opt'"
            ;;
        -*)
            # split opts -abc into -a -b -c
            set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@"
            ;;
        *)
            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" ] || [ "$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

if [ -n "$custom_rootfs" ] && [ "$backingstore" != "dir" ]; then
   echo "--dir is only valid with -B dir"
fi

case "$backingstore" in
    dir|lvm|none|btrfs|_unset) :;;
    *)
        echo "$(basename $0): '$backingstore' is not known (try 'none', 'dir', '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" ] || [ "$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 2>&1
    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
    lxc_config="/etc/lxc/default.conf"
    echo
    echo "$(basename $0): No config file specified, using the default config $lxc_config"
fi

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

if [ ! -z "$lxc_template" ]; then
    # Allow for a path to be provided as the template name
    if [ -x "$lxc_template" -a $(echo "$lxc_template" | cut -c 1) = '/' ]; then
        template_path=$lxc_template
    else
        template_path=${templatedir}/lxc-$lxc_template
    fi

    if ! [ -x "$template_path" ]; then
        echo "$(basename $0): unknown template '$lxc_template'" >&2
        cleanup
    fi

    sum=$(sha1sum $template_path | cut -d ' ' -f1)
    echo "# Template used to create this container: $lxc_template" >> $lxc_path/$lxc_name/config
    if [ -n "$*" ]; then
        echo "# Parameters passed to the template: $*" >> $lxc_path/$lxc_name/config
    fi
    echo "# Template script checksum (SHA-1): $sum" >> $lxc_path/$lxc_name/config
    echo "" >> $lxc_path/$lxc_name/config
fi

cat $lxc_config >> $lxc_path/$lxc_name/config
echo "" >> $lxc_path/$lxc_name/config

if [ -n "$custom_rootfs" ]; then
    if grep -q "lxc.rootfs" $lxc_path/$lxc_name/config ; then
        echo "configuration file already specifies a lxc.rootfs"
        exit 1
    fi
    if [ -d "$custom_rootfs" ]; then
        echo "specified rootfs ($custom_rootfs) already exists.  Bailing."
        exit 1
    fi
    echo "lxc.rootfs = $custom_rootfs" >> $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
    $template_path --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"
