#!/bin/bash
#
# @file imgb.sh
#
# @copyright
# Copyright (c) 2014 by Cisco Systems, Inc.
# All rights reserved.
#
# @brief
# Utility to manipulate OVA and rootfs files for IOx project
#
# @changelog
# 2014/09/30 - Original version
#
# @date 2014/09/24
#
FULL_PROG_NAME=$0
PROG_NAME=$(basename $FULL_PROG_NAME)
PROG_ARGS="$*"

PROG_STEP=0

# dev setup - for use within Cisco only
#SCORE_WS=/nobackup/${USER}/ws/all-cwan_iox_score_dev-clean
if [ ! -z "${SCORE_WS}" ] ; then
	FAKEROOT=${SCORE_WS}/hostTools/tools/fakeroot-1.11/fakeroot
	PACKING=${SCORE_WS}/userspace/cisco/vman/packing.sh
fi

# need this one for logging timestamp
DATE=$(which date)

# utilities this script depends on
UTILS="tar cpio gzip gunzip zcat rm find grep readlink packing.sh fakeroot"

# rootfs name in IOx OVA archive
ROOTFS_NAME=e500v2-linux-guest.rootfs

# globals
DEBUG=0			# for this script
cmd_array=()	# array containing command definitions

#TRACE_LOG=1	# toggle this off for release
TRACE_LOG=0


#
# Function to create a file descriptor to send log to
# This way the user can redirect log messages to a file
# example: $0 <options> 5>>/tmp/iox_sdk.logs
#
function setup_logging() {
	# create fd for logging
	if [[ ! -e /proc/$$/fd/5 ]]; then
		exec 5>/dev/tty
	fi
}

#
# Used for debugging this script, low level debugging
# Prepend message by invoking function's name and line number
#
# @param[in] $* Message to be printed unconditionally
# @return always 0
#
function trace_log() {
	[ $TRACE_LOG -ne 0 ] && echo -e "$(${DATE} '+%F %T') [TRACE] ${FUNCNAME[1]}() ${BASH_LINENO[0]}: ${@}"
	return 0;
}


#
# Log a message, useful for debugging this script
#
# @param $1 log level
# @param $* message to print
# @return always 0
#
function log() {
	local level=$1
	#echo "DEBUG: $DEBUG, level: $level"

	# don't print debug if disabled
	[ $DEBUG -eq 0 ] && [ "${level}" == DEBUG ] && return 1;
	# print formatted string
	shift
	echo -e "$(${DATE} '+%F %T') [$level] ${FUNCNAME[1]}() ${BASH_LINENO[0]}: ${@}" >&5
	return 0;
}


#
# Print a message to the console for the end-user
#
# @param $* message to print
# @return always 0
#
function print_user_msg() {
	echo -e "$*"
}


function print_header() {
	((PROG_STEP++))
	echo "***************************************************************************"
	echo -e "\033[31mStep ${PROG_STEP}.\e[0m $*"
	echo "***************************************************************************"
}


#
# Usage function
# Prints help message for this program
#
function usage() {
cat << EOF
Usage $PROG_NAME [COMMAND] [OPTION]
Utility to facilitate rootfs operations

Global options:
  -d	debug
  -c	command name (see below)
  -h	help, print this output

Commands:
EOF

	# add command define in the cmd array
	get_cmds_usage

cat << EOF

Examples:
  $PROG_NAME -c list-rootfs -f my.rootfs	# Output my.rootfs' content

Report bugs to <iox-support@cisco.com>.
EOF
}


#
# Prints the content of the cmd array
#
function dbg_print_cmd_array() {
	local cmd_entry cmd_name cmd_fn_name
	log "DEBUG" "Registered command entries:"
	for ((i=0; i < ${#cmd_array[@]}; i++)); do
		cmd_entry=(${cmd_array[$i]})
		cmd_name="${cmd_entry[0]}"
		cmd_fn_name="${cmd_entry[1]}"
		log DEBUG "[$i] { name: $cmd_name, fn: $cmd_fn_name },"
	done
}


#
# Utility function to retrieve a file based on its index for a given command
# name
#
# @param[in]  $1 Name of the command
# @param[in]  $2 Field number to retrieve
# @param[out] $3 var name
# @return 0 if successful, non-zero otherwise
#
function cmd_array_get_field() {
	local cmd_entry=
	for ((i=0; i < ${#cmd_array[@]}; i++)); do
		# get next cmd entry
		cmd_entry=(${cmd_array[$i]})
		# check if cmd name match current entry
		if [ "${cmd_entry[0]}" == "$1" ] ; then	
			eval "$3"=${cmd_entry[$2]}
			return 0;
		fi
	done
	return 1;
}


#
# Return the function name (from within this script) for a given command name
# from the global cmd array.
#
# @param[in]  $1 Name of the command
# @param[out] $2 Name of the variable to set the function name to
# @return 0 if successful, non-zero otherwise
#
function cmd_array_get_fn_name() {
	cmd_array_get_field $1 1 $2
	return $?
}


#
# Retrieve the check/validate function name for a cmd name
#
# @param[in]  $1 Name of the command
# @param[out] $2 Name of the variable to set the function name to
# @return 0 if successful, non-zero otherwise
#
function cmd_array_get_chk_fn_name() {
	cmd_array_get_field $1 2 $2
	return $?
}


#
# Check if a command name is a valid registered command
#
# @param[in]  $1 Name of the command
# @param[out] $2 Name of the variable to set the function name to
#
function cmd_array_is_cmd_valid() {
	local not_use=
	cmd_array_get_fn_name $1 not_use
	return $(($?-1));
}


#
# Print to stdout the help of each cmd entry from the cmd array
# It is used by the "usage" function
#
function get_cmds_usage() {
	local cmd_entry txt
	for ((i=0; i < ${#cmd_array[@]}; i++)); do
		cmd_entry=(${cmd_array[$i]})
		# print the command name
		echo -e "  ${cmd_entry[0]}"
		# print options
		txt="\t\t${cmd_entry[3]}"
		echo -e "${txt//_/ }\n"
	done
}


#
# Build cmd array with definitions
# Each entries has:
#	1-> name (just a string to identify the command)
#	2-> execute function for the command
#	3-> validation check for the command
#	4-> help/usage string
#
function init_cmd_array() {

	# ROOTFS OPERATIONS
	cmd_entry=(list-rootfs \
				exe_list_rootfs \
				chk_list_rootfs \
				'Lists_the_content_of_a_rootfs_image,_use_-f_<rootfs>');
		cmd_array[${#cmd_array[*]}]="${cmd_entry[@]}";

	cmd_entry=(extract-rootfs \
				exe_extract_rootfs \
				chk_extract_rootfs \
				'Extract_a_rootfs_to_a_directory,_use_-f_<rootfs>_-t_<destination>');
		cmd_array[${#cmd_array[*]}]="${cmd_entry[@]}";

	cmd_entry=(repack-rootfs \
				exe_repack_rootfs \
				chk_repack_rootfs \
				'Repacks_a_rootfs,_use_-f_<original_rootfs>_-o_<output_rootfs>_-s_<script>_-t_<extract_destination_dir>');
		cmd_array[${#cmd_array[*]}]="${cmd_entry[@]}";

	# OVA OPERATIONS
	cmd_entry=(list-rootfs-from-ova \
				exe_list_rootfs_ova \
				chk_list_rootfs_ova \
				'Lists_the_content_of_an_ova_image,_use_-f_<ova>');
		cmd_array[${#cmd_array[*]}]="${cmd_entry[@]}";

	cmd_entry=(extract-ova \
				exe_extract_ova \
				chk_extract_ova \
				'Extract_an_ova_to_a_directory,_use_-f_<ova>_-t_<destination>');
		cmd_array[${#cmd_array[*]}]="${cmd_entry[@]}";

	# SETUP OPERATIONS
	cmd_entry=(test-env \
				exe_test_env \
				chk_test_env \
				'Test_the_runtime_environment');
		cmd_array[${#cmd_array[*]}]="${cmd_entry[@]}";
	#cmd_array[0]=(list-rootfs cmd_list_rootfs '-l\tprint content');
}


#
# Goes through the $UTILS used by this script are available on the system
# 
# @param $1 verbose (0|1)
# @return 0 if all utilites were found, 1 if not
#
function define_utilities() {
	local tmp x cmd_ucase
	local missing=0

	# go through each utilities
	for x in $UTILS; do
		# get uppercase version of the utility's name
		cmd_ucase=$(echo "${x%%.*}" | tr '[:lower:]' '[:upper:]')
		# get the value of the variable - can be predefined or hardcoded location
		tmp=$(eval echo "\$${cmd_ucase}")
		log DEBUG "Looking for utility: $x"
		if [ -z "${tmp}" ] ; then
			# not defined, look for one in $PATH
			tmp=$(which $x 2>/dev/null)
			if [ $? -ne 0 ]; then
				# couldn't find utility in $PATH this is problematic
				((missing=missing+1))
				log ERROR "Could not find utility: $x"
				continue;
			fi
		fi
		# at this point we know the utility's full path, assign it to the 
		# variable
		eval "$cmd_ucase"=$tmp
		log DEBUG "Found utility: $x at: $tmp"
		if [ $1 -ne 0 ] ; then
			print_user_msg "Found utility: $x at: $tmp"
		fi
	done
	if [ $missing -ne 0 ]; then
		return 1;
	fi
	return 0;
}


#
# Check function, no real need for this since this exe_test_env() is actually
# doing a "check" as its primary purpose.
#
function chk_test_env() {
	return 0;
}


#
# Check if all utilities are available on the workstation
# Invokes the definine utilites in verbose mode.
#
function exe_test_env() {
	print_user_msg "***********************************"
	print_user_msg "Looking for utilities on the system"
	print_user_msg "***********************************"
	define_utilities 1
	return 0;
}


#
# Validate and set globals that will be consumed by the exe_ function
#
# @param[in] $* program cmdline arguments
#
function chk_list_rootfs() {
	cmd_args=()
	# give a chance for the help (even if a command was not provided)
	# first : is to prevent the "illegal option" message
	OPTIND=0
	while getopts ":f:dc:h:" opt; do
		case $opt in
			f)
				cmd_args[0]=$OPTARG
				log DEBUG "Setting file value to $OPTARG";
				;;
			dch)
				;;
		esac
	done # while getopts

	log DEBUG "File: ${cmd_args[0]}"
	[   -z "${cmd_args[0]}" ] && log ERROR "Missing mandatory argument -f" && return 1;
	[ ! -f "${cmd_args[0]}" ] && log ERROR "Specified file does not exist: ${cmd_args[0]}" && return 1;

	return 0;
}


#
# List the content of a rootfs from an OVA
#
function exe_list_rootfs() {
	log DEBUG "Command arguments array: ${cmd_args[@]}"
	local file="${cmd_args[0]}"
	local shell_cmd="${GUNZIP} -dfc ${file} | ${CPIO} -ivt"

	print_header "Listing rootfs content for: $file"
	log DEBUG "Running: $shell_cmd"
	eval $shell_cmd
	return $?
}


#
# Check command line arguments for "extract-rootfs" command.
# Goes through cmdline arguments and fill up cmd_args.
#
# @return 0 if all arguments are provided and valid, 1 otherwise
#
function chk_extract_rootfs() {
	cmd_args=()
	OPTIND=0	# reset getopts
	while getopts ":f:t:dc:h" opt; do
		case $opt in
			f)
				cmd_args[0]=$OPTARG
				log DEBUG "Setting file value to $OPTARG";
				;;
			t)
				cmd_args[1]=$OPTARG
				log DEBUG "Setting destination directory value to $OPTARG";
				;;
			dch)
				;;
		esac
	done # while getopts

	# check the file
	log DEBUG "File: ${cmd_args[0]}"
	[   -z "${cmd_args[0]}" ] && log ERROR "Missing mandatory argument -f" && return 1;
	[ ! -f "${cmd_args[0]}" ] && log ERROR "Specified file does not exist: ${cmd_args[0]}" && return 1;

	# check the target directory
	log DEBUG "Target directory: ${cmd_args[1]}"
	[   -z "${cmd_args[1]}" ] && log ERROR "Missing mandatory argument -t" && return 1;
	[ ! -e "${cmd_args[1]}" ] && log ERROR "Specified target does not exist: ${cmd_args[1]}" && return 1;
	[ ! -d "${cmd_args[1]}" ] && log ERROR "Specified target is not a directory: ${cmd_args[1]}" && return 1;
	[ ! -z "$(shopt -s nullglob;shopt -s dotglob;echo ${cmd_args[1]}/*)" ] && log ERROR "Specified target directory must be empty" && return 1;

	return 0;
}


#
# Extracts a rootfs image under an empty existing directory
#
# From manual:
#	zcat $1 | cpio -i -d -H newc --no-absolute-filenames
#
function exe_extract_rootfs() {
	log DEBUG "Command arguments array: ${cmd_args[@]}"
	local rootfs="$(${READLINK} -f ${cmd_args[0]})"
	local dir="${cmd_args[1]}"
	local shell_cmd="(cd ${dir} && ${ZCAT} ${rootfs} | ${CPIO} -iu --no-absolute-filenames)"
	print_header "Extracting rootfs: ${rootfs} under: ${dir}..."
	log DEBUG "Running: $shell_cmd"
	eval $shell_cmd
	return $?
}


#
# Check command line arguments for "repack-rootfs" command.
# Goes through cmdline arguments and fill up cmd_args.
#
# @return 0 if all arguments are provided and valid, 1 otherwise
#
function chk_repack_rootfs() {
	trace_log "args: $*"
	cmd_args=()
	OPTIND=0
	while getopts ":f:s:o:dc:ht:" opt; do
		trace_log "processing arg: $opt"
		case $opt in
			f)
				cmd_args[0]=$OPTARG
				log DEBUG "Setting file value to $OPTARG";
				;;
			s)
				cmd_args[1]=$OPTARG
				log DEBUG "Setting script value to $OPTARG";
				;;
			o)
				cmd_args[2]=$OPTARG
				log DEBUG "Setting output value to $OPTARG";
				;;
			t)
				cmd_args[3]=$OPTARG
				log DEBUG "Setting extract dir value to $OPTARG";
				;;
			dch)
				;;
		esac
	done # while getopts

	# check the file
	[   -z "${cmd_args[0]}" ] && log ERROR "Missing mandatory argument -f" && return 1;
	[ ! -f "${cmd_args[0]}" ] && log ERROR "Specified file does not exist: ${cmd_args[0]}" && return 1;
	log DEBUG "Validation for the rootfs: ${cmd_args[0]} passed"

	# check the script
	[   -z "${cmd_args[1]}" ] && log ERROR "Missing mandatory argument -s" && return 1;
	[ ! -f "${cmd_args[1]}" ] && log ERROR "Specified script does not exist: ${cmd_args[1]}" && return 1;
	[ ! -x "${cmd_args[1]}" ] && log ERROR "Specified script must be an executable: ${cmd_args[1]}" && return 1;
	log DEBUG "Validation check for the user's script: ${cmd_args[1]} passed"

	# check the output directory
	[   -z "${cmd_args[2]}" ] && log ERROR "Missing mandatory argument -o" && return 1;
	[   -f "${cmd_args[2]}" ] && log ERROR "Specified output already exists: ${cmd_args[2]}" && return 1;
	log DEBUG "Output: ${cmd_args[2]}"
	log DEBUG "Validation for the output directory: ${cmd_args[2]} passed"

	return 0;
}


#
# Note:
# cmd_args[3], rootfs dir (-t) already filled by chk_repack_rootfs()
#
function repack_rootfs_create_rootfs_dir() {
	# check the output directory
	if [ ! -z "${cmd_args[3]}" ] ; then
		if [ ! -d "${cmd_args[3]}" ] ; then
			log ERROR "Specified extract directory ${cmd_args[3]} does not exist" && return 1;
			return 1;
		fi
	else
		# not specified, create one:
		local new_dir="$(pwd)"
		new_dir="${new_dir}/rootfs.ext.$(${DATE} '+%Y%m%d-%H%M%S')"
		log DEBUG "Extract dir: ${new_dir}"
		# create it
		mkdir -p "$new_dir";
		cmd_args[3]="${new_dir}"
	fi
	log DEBUG "Extract dir: ${cmd_args[3]}"
	log DEBUG "Validation for the extract dir directory: ${cmd_args[3]} passed"
	return 0;
}


#
# This function is one of the main of this tool.
#
# Steps:
# 1) Call ourselves from within fakeroot for rootfs operations
# 2) Extract rootfs
# 3) Run user's script
# 4) Repack rootfs
#
# Note that this is a tricky command, we need to be reentrant and check if
# we are the 2nd instance, if not, call ourselves
#
function exe_repack_rootfs() {
	log DEBUG "Command arguments array: ${cmd_args[@]}"
	local rootfs="${cmd_args[0]}"
	local script="${cmd_args[1]}"
	local new_rootfs="${cmd_args[2]}"
	local shell_cmd="CISCO_IMGB_J=1 ${FAKEROOT} ${FULL_PROG_NAME} ${PROG_ARGS}"
	log DEBUG "params: { rootfs=${rootfs}, script=${script}, new_rootfs=${new_rootfs} }"
	if [ -z "${CISCO_IMGB_J}" ] ; then
		log DEBUG "Invoking: ${shell_cmd}"
		eval $shell_cmd
		return $?
	fi
	if [ -z "${FAKEROOTKEY}" ] ; then
		# this is in the second instance of this program but we're not in
		# fakeroot? this is a problem, abandon ship
		log ERROR "Couldn't run in fakeroot environment, aborting"
		exit 1;
	fi

	# Ensure that the extract directory target exist
	# this can't be done in the check function since we want to do this only once 
	repack_rootfs_create_rootfs_dir
	[ $? -ne 0 ] && log ERROR "Could not proceed with extract directory: ${cmd_args[3]}" && return 1;
	local rootfs_extract_dir="${cmd_args[3]}"

	# should be running from within fakeroot
	# Step 1 - extract rootfs
	cmd_args[0]="${rootfs}"
	cmd_args[1]="${rootfs_extract_dir}"

	if [ -d "${rootfs_extract_dir}" ] && 
		[ ! -z "$(shopt -s nullglob;shopt -s dotglob;echo ${rootfs_extract_dir}/*)" ]; then
		log WARNING "rootfs extract dir: ${rootfs_extract_dir} has content, removing..."
		\rm -rf ${rootfs_extract_dir}/*
	fi
	exe_extract_rootfs

	if [ $? -ne 0 ]; then
		log "ERROR" "Error while extracting the rootfs: ${rootfs} at: ${rootfs_extract_dir}, aborting."
		return 1;
	fi
	# Step 2 - run user's scripts
	print_header "Invoking user's script: $script ${rootfs_extract_dir}"
	${script} "${rootfs_extract_dir}"
	local rc=$?
	print_user_msg "**************************************"
	print_user_msg "Script completed with exit status: $rc"
	print_user_msg "**************************************"
	if [ $rc -ne 0 ]; then
		log "ERROR" "Error reported by user's script: ${script}, aborting."
		return 1;
	fi
	
	# Step 3 - pack rootfs
	print_header "Packing rootfs: ${new_rootfs}"
	cmd_args[0]="${extract_dir}"
	cmd_args[1]="$(${READLINK} -f "${new_rootfs}")"
	exe_pack_rootfs
	if [ $? -ne 0 ]; then
		log "ERROR" "Error packing the new rootfs: ${new_rootfs}, aborting."
		return 1;
	fi
	return 0;
}


#
# This function reconstruct a rootfs image from a directory
# Utility function called by exe_repack_rootfs
#
# https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt
#
function exe_pack_rootfs() {
	local rootfs_extracted_dir="${cmd_args[0]}"
	local output_rootfs="${cmd_args[1]}"
	log DEBUG "params: { rootfs_extract_dir=${rootfs_extract_dir}, output_rootfs=${output_rootfs} }"

	# pack it
	if [ ! -d "${rootfs_extract_dir}" ] ; then
		echo "Error, file is not a directory: ${rootfs_extract_dir}";
		return 1;
	fi
	# from manual
	print_user_msg "Packing rootfs: ${output_rootfs} with content at: ${rootfs_extract_dir}..."
	#(cd "${rootfs_extract_dir}"; find . | ${FAKEROOT} cpio -o -H newc | gzip) > "$2"
	(cd "${rootfs_extract_dir}"; ${FIND} . | ${CPIO} -o -H newc > initramfs.cpio; ${GZIP} initramfs.cpio; mv initramfs.cpio.gz "${output_rootfs}")
}


#
# Validate and set globals that will be consumed by the exe_ function
#
# @param[in] $* program cmdline arguments
#
function chk_list_rootfs_ova() {
	# same processing than the other check function
	chk_list_rootfs $*
	return $?
}


#
# List the content of a rootfs from an OVA
#
function exe_list_rootfs_ova() {
	log DEBUG "Command arguments array: ${cmd_args[@]}"
	local file="${cmd_args[0]}"
	local shell_cmd="${TAR} xOf ${file} ${ROOTFS_NAME} | ${GUNZIP} -dfc | ${CPIO} -ivt"
	if [ ! -e "${file}" ]; then
		log ERROR "File doesn't exist: ${file}"
		return 1;
	fi
	log DEBUG "Running: $shell_cmd"
	eval $shell_cmd
}


#
# Check, validates and populate cmd_args[]
#
# Simply invoke chk_extract_rootfs() since it does the same parsing and
# validation
#
function chk_extract_ova() {
	chk_extract_rootfs $*
	return $?;
}


#
# Extracts an ova to an existing empty directory
#
function exe_extract_ova() {
	log DEBUG "Command arguments array: ${cmd_args[@]}"
	local img="$(${READLINK} -f ${cmd_args[0]})"
	local dir="${cmd_args[1]}"
	local shell_cmd="${TAR} xf ${img} -C ${dir} > /dev/null"
	log INFO "Extracting ova: ${img} under: ${dir}..."
	log DEBUG "Running: $shell_cmd"
	eval $shell_cmd
	return $?
}


################################## OLD BEGIN ##################################

#
# $1 ova content dir
# $2 staging dir (to create)
#
function pack_ova() {
	echo "pack_ova: $*"
	local ova_ext_dir=$1
	local ova_staging_dir=$2
	mkdir ${ova_staging_dir}

	# create a staging area
	cp ${ova_ext_dir}/* ${ova_staging_dir} # this wont copy .d so its safe
	rm ${ova_staging_dir}/*rootfs*

	# first pack the rootfs - create cpio, gz
	pack_rootfs ${ova_ext_dir}/e500v2-linux-guest.rootfs.d ${ova_staging_dir}/e500v2-linux-guest.rootfs

	# invoke packing script
	${PACKING} -a 0 -s ${SCORE_WS}/userspace/cisco/vman/etc ${ova_staging_dir}
}

# $1 OVA
function explode() {
	local ova_ext_dir=${ROOT_BASE}/$(basename ${1}).d
	# extract OVA to location
	extract_ova $1 ${ova_ext_dir}
	if [ $? -ne 0 ] ; then
		return 1;
	fi
	# uncompress rootfs from OVA extracted location

	# find the rootfs file
	rootfs_file=$(ls ${ova_ext_dir}|${GREP} rootfs|head -n1)
	echo "rootfs file: ${rootfs_file}"
	if [ $? -ne 0 ] || [ ! -e "${ova_ext_dir}/${rootfs_file}" ] ; then
		return 1;
	fi
	extract_rootfs ${ova_ext_dir}/${rootfs_file} ${ova_ext_dir}/${rootfs_file}.d
	return 0;
}

# old ova dir
# new ova dir
function prepare() {
	# copy extracted version
	echo "prepare: $*"
	local src_dir=$1
	local dst_dir=$2

	# copy base
	cp -ra $src_dir $dst_dir

	#dirs="/dev"
	#dirs="/dev /lib /bin /etc"
	#dirs="bin/ usr/ lib/ sbin/" STILL FAST
	#dirs="bin/ usr/ lib/"
	dirs=""

	for x in $dirs; do
		# replace x
		echo "Prepare: removing ${dst_dir}/e500v2-linux-guest.rootfs.d/$x"
		\rm -rf ${dst_dir}/e500v2-linux-guest.rootfs.d/$x
		# copy the fast one in
		cp -ra ${src_dir}/../test0902.ova.d/e500v2-linux-guest.rootfs.d/$x ${dst_dir}/e500v2-linux-guest.rootfs.d
	done

	#files_to_remove="hotplug ldconfig udevadm udevd sln shutdown"
	files_to_remove="hotplug udevd udevadm"
	for x in $files_to_remove; do
		\rm -rf ${dst_dir}/e500v2-linux-guest.rootfs.d/sbin/$x
	done
}

################################## OLD END ####################################

#
# Process command line arguments
#
# @param[out] $1 name of the command to use
# @param[in]  $2 program cmdline args including command name (which should be the first)
#
function process_args() {
	local valid=1
	local call_usage=0
	local cmd_name_arg=
	local cmd_to_run=$1	# variable to set with the command name
	shift	# consume the variable name

	trace_log "process_args: $*"
	# process global args

	# give a chance for the help (even if a command was not provided)
	# first ":" is to prevent the "illegal option" message
	while getopts ":dhc:" opt; do
		#trace_log "check option: $opt"
		case $opt in
			c)
				cmd_name_arg=$OPTARG
				;;
			d)
				DEBUG=1;	# Turn on debugging of this script
				;;
			h)
				call_usage=1;
				;;
		esac
	done # while getopts
	[ $call_usage -eq 1 ] && usage && exit 0;

	# was not -h/usage, must have provided a valid command
	# first check if the command is valid
	[ -z "${cmd_name_arg}" ] && log ERROR "Must provide a command, use -c <command>" && usage && exit 1;

	cmd_array_is_cmd_valid $cmd_name_arg
	[ $? -eq 0 ] && log ERROR "Command is invalid: $cmd_name_arg, see usage" && usage && exit 1;
	
	# assign the value and return
	eval "${cmd_to_run}"=${cmd_name_arg}
	return 0;
}


#
# Main function
#
function main() {
	local cmd_name fn_ptr args_copy
	# internal structure initialization
	init_cmd_array
	setup_logging

	# retrieve cmd name based on cmd line args
	args_copy=("$@")
	process_args cmd_name $* # go through the program cmdline arguments

	# check if all utilities that we need are present on the system
	define_utilities 0
	[ $? -ne 0 ] && log ERROR "Missing utilities, aborting." && return 1;

	dbg_print_cmd_array # only write if debug is enabled
	[ -z "${cmd_name}" ] && log ERROR "Could not find command name." && return 1;

	# run the check function
	cmd_array_get_chk_fn_name ${cmd_name} fn_ptr
	[ -z "${fn_ptr}" ] && log ERROR "Could not find validation function for command: ${cmd_name}." && return 1;
	$fn_ptr $*
	[ $? -ne 0 ] && log ERROR "Validation failed" && return 1;

	# retrieve function name registered for the cmd
	fn_ptr=	#reset
	cmd_array_get_fn_name ${cmd_name} fn_ptr
	[ -z "${fn_ptr}" ] && log ERROR "Could not find function for command: ${cmd_name}." && return 1;
	log DEBUG "Command to run is: $cmd_name, function: ${fn_ptr}"

	# invoke the function
	$fn_ptr $file
	return $?;
}


#########################################
main $*
exit $?

# vim: set ts=4 sw=4 tw=0 noet :
