#!/bin/sh #-------------------------------------------------------------------------+ # Copyright (C) 2016 Matt Churchyard (churchers@gmail.com) # All rights reserved # # Redistribution and use in source and binary forms, with or without # modification, are permitted providing that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. CMD_VALID_LIST="init,switch,datastore,image,get,set,list,create,destroy,rename,install,start,stop,restart" CMD_VALID_LIST="${CMD_VALID_LIST},add,reset,poweroff,startall,stopall,console,iso,img,configure,passthru,_run" CMD_VALID_LIST="${CMD_VALID_LIST},info,clone,snapshot,rollback,migrate,version,usage" # cmd: vm ... # # handle simple information commands that don't need any # priviledged access or bhyve support # # @param string _cmd the command right after 'vm ' # cmd::parse_info(){ local _cmd cmd::find "_cmd" "$1" "${CMD_VALID_LIST}" case "${_cmd}" in version) util::version && exit ;; usage) util::usage ;; esac } # cmd: vm ... # # process the vm command line to see which function is requested # # @param string _cmd the command right after 'vm ' # cmd::parse(){ local _cmd # try to find a matching command cmd::find "_cmd" "$1" "${CMD_VALID_LIST}" || util::usage shift case "${_cmd}" in init) util::setup switch::init ;; switch) cmd::parse_switch "$@" ;; datastore) cmd::parse_datastore "$@" ;; image) cmd::parse_image "$@" ;; get) core::get "$@" ;; set) core::set "$@" ;; list) core::list "$@" ;; create) core::create "$@" ;; destroy) core::destroy "$@" ;; rename) core::rename "$@" ;; install) core::install "$@" ;; start) core::start "$@" ;; stop) core::stop "$@" ;; restart) core::restart "$@" ;; add) core::add "$@" ;; reset) core::reset "$@" ;; poweroff) core::poweroff "$@" ;; startall) core::startall ;; stopall) core::stopall ;; console) core::console "$@" ;; iso) core::iso "$@" ;; img) core::img "$@" ;; configure) core::configure "$@" ;; passthru) core::passthru ;; _run) vm::run "$@" ;; info) info::guest "$@" ;; clone) zfs::clone "$@" ;; snapshot) zfs::snapshot "$@" ;; rollback) zfs::rollback "$@" ;; migrate) migration::run "$@" ;; *) util::err "unknown command '${_user_cmd}'. please run 'vm usage' or view the manpage for help" ;; esac } # cmd: vm switch ... # # parse switch command # we've already shifted once, so $1 is the switch function # # @param string _cmd the command right after 'vm switch ' # cmd::parse_switch(){ local _cmd # try to find a matching command cmd::find "_cmd" "$1" "create,list,destroy,add,remove,vlan,nat,address,private,info" || util::usage shift case "${_cmd}" in create) switch::create "$@" ;; list) switch::list ;; destroy) switch::remove "$@" ;; add) switch::add_member "$@" ;; remove) switch::remove_member "$@" ;; vlan) switch::vlan "$@" ;; nat) switch::nat "$@" ;; address) switch::address "$@" ;; private) switch::private "$@" ;; info) info::switch "$@" ;; *) util::err "unknown command '${_user_cmd}'. please run 'vm usage' or view the manpage for help" ;; esac } # cmd: vm datastore ... # # parse a datastore command # # @param string _cmd the command after 'vm datastore ...' # cmd::parse_datastore(){ local _cmd # try to find a matching command cmd::find "_cmd" "$1" "list,add,remove,iso,img" || util::usage shift case "${_cmd}" in list) datastore::list ;; add) datastore::add "$@" ;; remove) datastore::remove "$@" ;; iso) datastore::iso "$@" ;; img) datastore::img "$@" ;; *) util::err "unknown command '${_user_cmd}'. please run 'vm usage' or view the manpage for help" ;; esac } # cmd 'vm image ...' # parse the image command set # # @param string _cmd the command after 'vm image ' # cmd::parse_image(){ local _cmd [ -z "${VM_ZFS}" ] && util::err "\$vm_dir must be a ZFS datastore to use these functions" # try to find a matching command cmd::find "_cmd" "$1" "list,create,provision,destroy" || util::usage shift case "${_cmd}" in list) zfs::image_list ;; create) zfs::image_create "$@" ;; provision) zfs::image_provision "$@" ;; destroy) zfs::image_destroy "$@" ;; *) util::err "unknown command '${_user_cmd}'. please run 'vm usage' or view the manpage for help" ;; esac } # many commands accept the same arguments (force being the obvious one) # provide a function to parse these so we don't have to keep # repeating the same getopt code. the return value here is the number # of arguments the caller needs to shift. # # note that start/install/_run use -f for foreground mode # # @param _arglist[multiple] the callers $@ # @return number of arguments caller should shift over # cmd::parse_args(){ local _opt _count while getopts fitv _opt; do case ${_opt} in f) VM_OPT_FORCE="1" VM_OPT_FOREGROUND="1" ;; i) VM_OPT_INTERACTIVE="1" ;; t) VM_OPT_TMUX="1" ;; v) VM_OPT_VERBOSE="1" ;; esac done [ -n "${VM_OPT_FOREGROUND}" ] && [ -n "${VM_OPT_INTERACTIVE}" ] && \ util::err "foreground and interactive mode are mutually exclusive" return $((OPTIND - 1)) } # try to match part of a command name against a list of valid commands # if we find more than one match we return an error # if we only get one match, return the full command name # # @param string _var variable to put full command name into # @param string _user_cmd the value provided by the user # @param string _valid comma-separated list of valid choices # @return success if we find one match # cmd::find(){ local _var="$1" local _user_cmd="$2" local _valid="$3" local _opt _choice _found="" local IFS="," [ -n "${_user_cmd}" ] || util::err "no command specified" for _opt in ${_valid}; do # exact match? if [ "${_user_cmd}" = "${_opt}" ]; then setvar "${_var}" "${_opt}" return 0 fi if echo "${_opt}" | grep -iqs "^${_user_cmd}"; then [ -n "${_found}" ] && util::err "ambiguous command '${_user_cmd}'" _found=1 _choice="${_opt}" fi done [ -z "${_found}" ] && return 1 setvar "${_var}" "${_choice}" }