#!/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. # 'vm datastore list' # show configured datastores # datastore::list(){ local _format="%-15s %-11s %-25s %s" local _name _type _dataset _path _spec # headings printf "${_format}\n" "NAME" "TYPE" "PATH" "ZFS DATASET" # add the default datastore _name="default" _path="${vm_dir}" # get the type and path if [ "${VM_ZFS}" ]; then _type="zfs" _dataset="${VM_ZFS_DATASET}" else _type="directory" _dataset="-" fi printf "${_format}\n" "${_name}" "${_type}" "${_path}" "${_dataset}" for _name in ${VM_DATASTORE_LIST}; do [ "${_name}" = "default" ] && continue config::core::get "_spec" "path_${_name}" [ -z "${_spec}" ] && continue if [ "${_spec%%:*}" = "zfs" ]; then _type="zfs" _dataset="${_spec#*:}" datastore::__resolve_path "_path" "${_spec}" elif [ "${_spec%%:*}" = "iso" ]; then _type="iso" _path="${_spec#*:}" _dataset="-" elif [ "${_spec%%:*}" = "img" ]; then _type="img" _path="${_spec#*:}" _dataset="-" else _type="directory" _path="${_spec}" _dataset="-" fi printf "${_format}\n" "${_name}" "${_type}" "${_path}" "${_dataset}" done } # 'vm datastore add' # create a new datastore # # we don't try and create directories or datasets. # the user should do that first # # @param string _name datastore name # @param string _spec specification (either /path or zfs:dataset) # datastore::add(){ local _name="$1" local _spec="$2" local _mount _num=0 _curr [ -z "${_name}" -o -z "${_spec}" ] && util::usage util::check_name "${_name}" || util::err "invalid datastore name - '${_name}'" # check name not in use for _curr in ${VM_DATASTORE_LIST}; do [ "${_curr}" = "${_name}" ] && util::err "datstore '${_name}' already exists!" done # look for zfs if [ "${_spec%%:*}" = "zfs" ]; then # try to find mountpoint _mount=$(mount | grep "^${_spec#*:} " |cut -d' ' -f3) [ -z "${_mount}" ] && util::err "${_spec} doesn't seem to be a valid, mounted dataset" else # make sure it's a directory [ ! -d "${_spec}" ] && util::err "${_spec} doesn't seem to be a valid directory" _mount="${_spec}" fi # see if this is already our default datastore [ "${_mount}" = "${vm_dir}" ] && util::err "specified path already exists as default datastore" # save config::core::set "datastore_list" "${_name}" "1" config::core::set "path_${_name}" "${_spec}" [ $? -eq 0 ] || util::err "error saving settings to configuration file" } # remove a datastore # we don't actually delete anything, just remove from config # # @param string _name name of dataset # datastore::remove(){ local _name="$1" local _ds _found [ "${_name}" = "default" ] && util::err "cannot remove default datastore" for _ds in ${VM_DATASTORE_LIST}; do [ "${_ds}" = "${_name}" ] && _found="1" done # found the dataset? [ -z "${_found}" ] && util::err "unable to locate the specified dataset" config::core::remove "datastore_list" "${_name}" config::core::remove "path_${_name}" [ $? -eq 0 ] || util::err "error removing settings from configuration file" } # get the filesystem path for the specified dataset spec # this can either be just a path, or ZFS dataset # # @param string _var variable to put path into # @param string _spec the path spec (either directory or zfs:dataset) # @return non-zero on error # datastore::__resolve_path(){ local _var="$1" local _spec="$2" if [ "${_spec%%:*}" = "zfs" ]; then _mount=$(mount | grep "^${_spec#*:} " |cut -d' ' -f3) if [ -n "${_mount}" ]; then setvar "${_var}" "${_mount}" return 0 fi elif [ "${_spec%%:*}" = "iso" ] || [ "${_spec%%:*}" = "img" ]; then setvar "${_var}" "${_spec#*:}" return 0 else if [ -d "${_spec}" ]; then setvar "${_var}" "${_spec}" return 0 fi fi setvar "${_var}" "" return 1 } # load list of datastores into VM_DATASTORE_LIST # # @modifies VM_DATASTORE_LIST # datastore::load(){ config::core::get "VM_DATASTORE_LIST" "datastore_list" VM_DATASTORE_LIST="default${VM_DATASTORE_LIST:+ }${VM_DATASTORE_LIST}" } # init global settings for a vm # we take a guest name and try to find it in all # datastores. we don't allow duplicate names, and # if there is, we will just return the first found. # # @param string _guest guest name # @return non-zero on error # @modifies VM_DS_NAME VM_DS_PATH VM_DS_ZFS VM_DS_ZFS_DATASET # datastore::get_guest(){ local _guest="$1" local _ds _spec _path _found _zfs _dataset # look in default store if [ -f "${vm_dir}/${_guest}/${_guest}.conf" ]; then _found="1" _ds="default" _path="${vm_dir}" _zfs="${VM_ZFS}" _dataset="${VM_ZFS_DATASET}" fi # look on other datastores if [ -z "${_found}" ]; then for _ds in ${VM_DATASTORE_LIST}; do [ "${_ds}" = "default" ] && continue config::core::get "_spec" "path_${_ds}" if [ "${_spec%%:*}" = "iso" ] || [ "${_spec%%:*}" = "img" ]; then continue fi datastore::__resolve_path "_path" "${_spec}" if [ -f "${_path}/${_guest}/${_guest}.conf" ]; then [ "${_spec%%:*}" = "zfs" ] && _zfs="1" && _dataset="${_spec#*:}" _found="1" break fi done fi # make sure we have a path [ -z "${_found}" ] && return 1 # set variables VM_DS_NAME="${_ds}" VM_DS_PATH="${_path}" VM_DS_ZFS="${_zfs}" VM_DS_ZFS_DATASET="${_dataset}" } # get the path and details for a datastore # put into same variables as datastore_get_guest # # @param string _ds datastore name # @return non-zero on error # @modifies VM_DS_PATH VM_DS_ZFS VM_DS_ZFS_DATASET # datastore::get(){ local _ds="$1" local _spec _path _zfs _dataset # check for default if [ "${_ds}" = "default" ]; then VM_DS_NAME="default" VM_DS_PATH="${vm_dir}" VM_DS_ZFS="${VM_ZFS}" VM_DS_ZFS_DATASET="${VM_ZFS_DATASET}" return 0 fi config::core::get "_spec" "path_${_ds}" [ -z "${_spec}" ] && return 1 # skip iso and img stores if [ "${_spec%%:*}" = "iso" ] || [ "${_spec%%:*}" = "img" ]; then return 1 fi datastore::__resolve_path "_path" "${_spec}" || return 1 [ "${_spec%%:*}" = "zfs" ] && _zfs="1" && _dataset="${_spec#*:}" # set variables VM_DS_NAME="${_ds}" VM_DS_PATH="${_path}" VM_DS_ZFS="${_zfs}" VM_DS_ZFS_DATASET="${_dataset}" } # get the path for an iso datastore # # @param string _ds datastore name # datastore::get_iso(){ local _ds="$1" # default? # we use the .iso subdir in that case if [ "${_ds}" = "default" ]; then VM_DS_PATH="${vm_dir}/.iso" return 0 fi config::core::get "_spec" "path_${_ds}" [ -z "${_spec}" ] && return 1 # should be an iso ds [ "${_spec%%:*}" = "iso" ] || return 1 datastore::__resolve_path "_path" "${_spec}" || return 1 VM_DS_PATH="${_path}" } # get the path for an img datastore # # @param string _ds datastore name # datastore::get_img(){ local _ds="$1" # default? # we use the .img subdir in that case if [ "${_ds}" = "default" ]; then VM_DS_PATH="${vm_dir}/.img" return 0 fi config::core::get "_spec" "path_${_ds}" [ -z "${_spec}" ] && return 1 # should be an img ds [ "${_spec%%:*}" = "img" ] || return 1 datastore::__resolve_path "_path" "${_spec}" || return 1 VM_DS_PATH="${_path}" } # add a datastore for iso files # # @param string _name the name of the datastore # @param string _path filesystem path # datastore::iso(){ local _name="$1" local _path="$2" [ -z "${_name}" -o -z "${_path}" ] && util::usage util::check_name "${_name}" || util::err "invalid datastore name - '${_name}'" # check name not in use for _curr in ${VM_DATASTORE_LIST}; do [ "${_curr}" = "${_name}" ] && util::err "datstore '${_name}' already exists!" done # make sure directory exists [ ! -d "${_path}" ] && util::err "specified directory does not appear to be valid" # save config::core::set "datastore_list" "${_name}" "1" config::core::set "path_${_name}" "iso:${_path}" [ $? -eq 0 ] || util::err "error saving settings to configuration file" } # add a datastore for img files # # @param string _name the name of the datastore # @param string _path filesystem path # datastore::img(){ local _name="$1" local _path="$2" [ -z "${_name}" -o -z "${_path}" ] && util::usage util::check_name "${_name}" || util::err "invalid datastore name - '${_name}'" # check name not in use for _curr in ${VM_DATASTORE_LIST}; do [ "${_curr}" = "${_name}" ] && util::err "datstore '${_name}' already exists!" done # make sure directory exists [ ! -d "${_path}" ] && util::err "specified directory does not appear to be valid" # save config::core::set "datastore_list" "${_name}" "1" config::core::set "path_${_name}" "img:${_path}" [ $? -eq 0 ] || util::err "error saving settings to configuration file" } # find an iso file by looking in the default location # and any "iso" datastores # # @param string _var variable name to put full iso path into # @param string _file iso filename to look for # @return int success if found # datastore::iso_find(){ local _var="$1" local _file="$2" local _ds _spec # given a full path? if [ -z "${_file%%/*}" ] && [ -r "${_file}" ]; then setvar "${_var}" "${_file}" return 0 fi # file exists in current dir? if [ -r "$(pwd)/${_file}" ]; then setvar "${_var}" "$(pwd)/${_file}" return 0 fi # look in default store if [ -r "${vm_dir}/.iso/${_file}" ]; then setvar "${_var}" "${vm_dir}/.iso/${_file}" return 0 fi for _ds in ${VM_DATASTORE_LIST}; do config::core::get "_spec" "path_${_ds}" [ "${_spec%%:*}" != "iso" ] && continue if [ -r "${_spec#*:}/${_file}" ]; then setvar "${_var}" "${_spec#*:}/${_file}" return 0 fi done return 1 } # find an img file by looking in the default location # and any "img" datastores # # @param string _var variable name to put full img path into # @param string _file img filename to look for # @return int success if found # datastore::img_find(){ local _var="$1" local _file="$2" local _ds _spec # given a full path? if [ -z "${_file%%/*}" ] && [ -r "${_file}" ]; then setvar "${_var}" "${_file}" return 0 fi # file exists in current dir? if [ -r "$(pwd)/${_file}" ]; then setvar "${_var}" "$(pwd)/${_file}" return 0 fi # look in default store if [ -r "${vm_dir}/.img/${_file}" ]; then setvar "${_var}" "${vm_dir}/.img/${_file}" return 0 fi for _ds in ${VM_DATASTORE_LIST}; do config::core::get "_spec" "path_${_ds}" [ "${_spec%%:*}" != "img" ] && continue if [ -r "${_spec#*:}/${_file}" ]; then setvar "${_var}" "${_spec#*:}/${_file}" return 0 fi done return 1 } # list iso files # datastore::iso_list(){ local _ds _spec _format="%-20s%s\n" printf "${_format}" "DATASTORE" "FILENAME" # look for default iso location [ -d "${vm_dir}/.iso" ] && ls -1 "${vm_dir}/.iso" | awk '{printf "'${_format}'","default",$1}' # look for iso datastores for _ds in ${VM_DATASTORE_LIST}; do config::core::get "_spec" "path_${_ds}" [ "${_spec%%:*}" != "iso" ] && continue [ -d "${_spec#*:}" ] && ls -1 ${_spec#*:} | awk '{printf "'${_format}'","'${_ds}'",$1}' done } # list img files # datastore::img_list(){ local _ds _spec _format="%-20s%s\n" printf "${_format}" "DATASTORE" "FILENAME" # look for default img location [ -d "${vm_dir}/.img" ] && ls -1 "${vm_dir}/.img" | awk '{printf "'${_format}'","default",$1}' # look for img datastores for _ds in ${VM_DATASTORE_LIST}; do config::core::get "_spec" "path_${_ds}" [ "${_spec%%:*}" != "img" ] && continue [ -d "${_spec#*:}" ] && ls -1 ${_spec#*:} | awk '{printf "'${_format}'","'${_ds}'",$1}' done }