# $HeadURL$ $LastChangedRevision$ ######################################################################## # # Modules # ######################################################################## ######################################################################## # # Characteristics of this module # ######################################################################## # (not needed in bash) ######################################################################## # # Public functions # ######################################################################## ######################################################################## # # Public functions: entry point related # ######################################################################## # (none) ######################################################################## # # Public functions: error stack related # ######################################################################## # (none) ######################################################################## # # Public functions: string/stream manipulation functions # ######################################################################## # (none) ######################################################################## # # Public functions: messaging functions # ######################################################################## #DIFFSYNC: internal miniade_internal() { local TEXT [ $# = 1 ] || miniade_internal "miniade_internal: $#: bad argument count" TEXT="$1" if [ "X$TEXT" != X- ]; then _miniade_display_message "INTERNAL ERROR: $TEXT" 0 error else while IFS= read -r TEXT; do _miniade_display_message "INTERNAL ERROR: $TEXT" 0 error done fi exit 2 } #DIFFSYNC: error miniade_error() { local TEXT [ $# = 1 ] || miniade_internal "miniade_error: $#: bad argument count" TEXT="$1" if [ "X$TEXT" != X- ]; then _miniade_display_message "ERROR: $TEXT" 1 error else while IFS= read -r TEXT; do _miniade_display_message "ERROR: $TEXT" 1 error done fi exit 1 } #DIFFSYNC: warning miniade_warning() { local TEXT [ $# = 1 ] || miniade_internal "miniade_warning: $#: bad argument count" TEXT="$1" if [ "X$TEXT" != X- ]; then _miniade_display_message "WARNING: $TEXT" 2 warning else while IFS= read -r TEXT; do _miniade_display_message "WARNING: $TEXT" 2 warning done fi } #DIFFSYNC: info miniade_info() { local TEXT [ $# = 1 ] || miniade_internal "miniade_info: $#: bad argument count" TEXT="$1" if [ "X$TEXT" != X- ]; then _miniade_display_message "INFO: $TEXT" 3 info else while IFS= read -r TEXT; do _miniade_display_message "INFO: $TEXT" 3 info done fi } #DIFFSYNC: debug miniade_debug() { local LEVEL TEXT SAVED_BASH_REMATCH [ $# = 2 ] || miniade_internal "miniade_debug: $#: bad argument count" LEVEL=$1 TEXT="$2" SAVED_BASH_REMATCH=( "${BASH_REMATCH[@]}" ) [[ $LEVEL =~ ^[1-9][0-9]*$ ]] || miniade_internal "miniade_debug: $LEVEL: invalid level" # Because BASH_REMATCH is global, we should (and did) save it to # another variable before calling '[[ ... =~ ... ]]', and restore # it afterwards. Unfortunately restoring it is only possible in # bash 5 onwards, as it's readonly in older bashes. (Of course, # if we can't restore it then there was no point in backing it # up, but we keep the code simpler by not worrying about that.) if [ ${BASH_VERSINFO[0]} -ge 5 ]; then BASH_REMATCH=( "${SAVED_BASH_REMATCH[@]}" ) fi if [ "X$TEXT" != X- ]; then _miniade_display_message "DEBUG[$LEVEL]: $TEXT" $LEVEL debug else while IFS= read -r TEXT; do _miniade_display_message "DEBUG[$LEVEL]: $TEXT" $LEVEL debug done fi } #DIFFSYNC: bad_usage miniade_bad_usage() { local PROGNAME miniade_get_progname PROGNAME echo "$PROGNAME: ERROR: type '$PROGNAME --help' for correct usage." >&2 exit 1 } ######################################################################## # # Public functions: filename manipulation functions # ######################################################################## # (none) ######################################################################## # # Public functions: file handle manipulation functions # ######################################################################## # (none) ######################################################################## # # Public functions: UUID-related # ######################################################################## # (none) ######################################################################## # # Public functions: file content manipulation functions # ######################################################################## # (none) ######################################################################## # # Public functions: option processing # ######################################################################## #DIFFSYNC: process_options miniade_process_options() { local NEW_ARGS_ARRAY_REF SPECIAL_OPTIONS_HANDLER HELP_HANDLER _MINIADE_EXTRA_SHIFTS STANDARD_OPTIONS_ENABLED local UNKNOWN_OPTIONS_ENABLED VERSION_HANDLER PATHS_HANDLER SAVED_BASH_REMATCH # Defaults for options HELP_HANDLER=_miniade_help_fallback VERSION_HANDLER=_miniade_version_fallback PATHS_HANDLER=_miniade_paths_fallback SPECIAL_OPTIONS_HANDLER=_miniade_special_options_fallback STANDARD_OPTIONS_ENABLED=true UNKNOWN_OPTIONS_ENABLED=false # Process options (to the miniade_process_options() call, not # to the program itself). while [ $# -ge 1 ]; do case $1 in --help-handler=*) HELP_HANDLER=${1#*=} ;; --usage-handler=*) miniade_warning "option '--usage-handler' has been superceded by '--help-handler'" HELP_HANDLER=${1#*=} ;; --version-handler=*) VERSION_HANDLER=${1#*=} ;; --paths-handler=*) PATHS_HANDLER=${1#*=} ;; --special-opts-handler=*) SPECIAL_OPTIONS_HANDLER=${1#*=} ;; --no-standard-opts) STANDARD_OPTIONS_ENABLED=false ;; --unknown-opts) UNKNOWN_OPTIONS_ENABLED=true ;; --) shift; break ;; # '-' may be an *argument* indicating to use stdin. -) break ;; -*) miniade_internal "miniade_process_options: $1: invalid option" ;; *) break ;; esac shift done # Process arguments [ $# -ge 1 ] || miniade_internal "miniade_process_options: $#: bad arg count" NEW_ARGS_ARRAY_REF=$1; shift # Guts # If the caller calls '[[ ... =~ ... ]]', then calls this function and then # reads BASH_REMATCH then they won't get what they expected because this # function changes it. So we must save and restore it. SAVED_BASH_REMATCH=( "${BASH_REMATCH[@]}" ) # Process each option on the *calling script's* command line. while [ $# -ge 1 ]; do _MINIADE_EXTRA_SHIFTS=0 # Standard options if $STANDARD_OPTIONS_ENABLED && [[ $1 =~ ^--debug=(0|[1-9][0-9]*)$ ]]; then _MINIADE_VERBOSELEVEL=${BASH_REMATCH[1]} elif $STANDARD_OPTIONS_ENABLED && [[ $1 = -d ]] && [ $# -ge 2 ] && [[ $2 =~ ^(0|[1-9][0-9]*)$ ]]; then _MINIADE_VERBOSELEVEL=$2 shift elif $STANDARD_OPTIONS_ENABLED && [[ $1 =~ ^(-n|--simulate)$ ]]; then _MINIADE_SIMULATE=true elif $STANDARD_OPTIONS_ENABLED && [[ $1 =~ ^--simulate=(true|false)$ ]]; then _MINIADE_SIMULATE=${1#*=} elif $STANDARD_OPTIONS_ENABLED && [[ $1 =~ ^(-v|--verbose)$ ]]; then _MINIADE_VERBOSELEVEL=3 elif $STANDARD_OPTIONS_ENABLED && [[ $1 =~ ^(-V|--version)$ ]]; then $VERSION_HANDLER elif $STANDARD_OPTIONS_ENABLED && [[ $1 =~ ^(-p|--paths)$ ]]; then $PATHS_HANDLER elif $STANDARD_OPTIONS_ENABLED && [[ $1 =~ ^(-h|--help)$ ]]; then $HELP_HANDLER elif [[ $1 = -- ]]; then shift break # '-' may be an *argument* indicating to use stdin. elif [[ $1 = - ]]; then break # For all non-standard options ... elif [[ $1 = -* ]]; then # ... if special options handler also didn't recognise it ... if ! $SPECIAL_OPTIONS_HANDLER "$@"; then # ... if we allow such options (typically when delegated to sub-main-like # function) then just break out of loop, without shifting off ... if $UNKNOWN_OPTIONS_ENABLED; then break # ... otherwise error ... else miniade_bad_usage fi # ... and if the special options handler *did* recognise it then # shift off as it asked. elif [ $_MINIADE_EXTRA_SHIFTS -ge 1 ]; then shift $_MINIADE_EXTRA_SHIFTS #else # : fi # For all non-option-like things just break out of loop so as to # pass them on to *argument* processing. else break fi # Shift off the option itself. shift done unset extra_shift # no longer needed # Because BASH_REMATCH is global, we should (and did) save it to # another variable before calling '[[ ... =~ ... ]]', and restore # it afterwards. Unfortunately restoring it is only possible in # bash 5 onwards, as it's readonly in older bashes. (Of course, # if we can't restore it then there was no point in backing it # up, but we keep the code simpler by not worrying about that.) if [ ${BASH_VERSINFO[0]} -ge 5 ]; then BASH_REMATCH=( "${SAVED_BASH_REMATCH[@]}" ) fi # Tell caller what's left in "$@", though caller must use it. eval "$NEW_ARGS_ARRAY_REF=( \"\$@\" )" } # Sanity checks and derivations # $SPECIAL_OPTIONS_HANDLER()'s $* is local to it, so it needs to call a function # wrapped around setting some variable private to miniade_process_options() # or write to some global variable. We *could* create some global variable # to contain the command line $* (like python's sys.argv[]) but that's just # too non-standard-shell. I choose the wrapper function. Here it is. Note # that python special_options_handler() doesn't need this, since it can # manipulate directly itself. miniade_extra_shift() { _MINIADE_EXTRA_SHIFTS=${1:-1} } ######################################################################## # # Public functions: special functions # ######################################################################## # (none) ######################################################################## # # Public functions: user related # ######################################################################## # (none) ######################################################################## # # Public functions: process management functions # ######################################################################## #DIFFSYNC: evaler miniade_evaler() { if $_MINIADE_SIMULATE; then echo "$@" else eval "$@" fi } ######################################################################## # # Public functions: temporary file related functions # ######################################################################## # (none) ######################################################################## # # Public functions: database related functions # ######################################################################## # (none) ######################################################################## # # Public functions: variable related functions # ######################################################################## # (none) ######################################################################## # # Public functions: locking related functions # ######################################################################## #DIFFSYNC: lock miniade_lock() { local LOCKFILE PID [ $# = 1 ] || miniade_internal "lock: bad argument count ($#)" LOCKFILE=$1 # Create a uniquely named temporary lock file miniade_debug 10 "lock: creating temporary lock file ..." echo $$ > $LOCKFILE.$$ # If can easily lock, return miniade_debug 10 "lock: sliding temporary lock into place ..." if ln $LOCKFILE.$$ $LOCKFILE 2>/dev/null; then rm $LOCKFILE.$$ return 0 fi # If lock is fresh, return miniade_debug 10 "lock: presumable lock file exists; checking if fresh ..." if miniade_lock_is_fresh $LOCKFILE; then rm -f $LOCKFILE.$$ return 1 fi # Remove empty or stale lock file miniade_warning "$LOCKFILE: empty or stale; removing ..." rm -f $LOCKFILE # If trying to lock again fails, error miniade_debug 10 "lock: again trying sliding temporary lock into place ..." if ln $LOCKFILE.$$ $LOCKFILE 2>/dev/null; then rm $LOCKFILE.$$ return 0 fi # If we get this far then something when wrong miniade_internal "lock: can't lock (hint: try manually running: ln $LOCKFILE.$$ $LOCKFILE)" } #DIFFSYNC: unlock miniade_unlock() { local LOCKFILE [ $# = 1 ] || miniade_internal "unlock: bad argument count ($#)" LOCKFILE=$1 rm -f $LOCKFILE } #DIFFSYNC: lock_is_fresh miniade_lock_is_fresh() { local LOCKFILE PID LOCKFILE=$1 miniade_debug 10 "lock_is_fresh: LOCKFILE=$LOCKFILE" # If lock file not empty and not stale; return if [ ! -f $LOCKFILE ]; then return 1 elif ! PID=$(cat $LOCKFILE); then miniade_internal "lock_is_fresh: failed to get PID from lockfile" elif [ "X$PID" = X ]; then return 1 elif [ ! -d /proc/$PID ]; then return 1 fi # If got to here then lock is fresh. return 0 } ######################################################################## # # Public functions: directory content management functions # ######################################################################## # (none) ######################################################################## # # Public functions: miscellaneous # ######################################################################## #DIFFSYNC: check_ssh_ok miniade_check_ssh_ok() { local UNAME SSH_OUTPUT for UNAME in "$@"; do SSH_OUTPUT="$(timeout 5 setsid ssh -n $UNAME echo OK || true)" [ "X$SSH_OUTPUT" = XOK ] || return 1 done # miniade_error "$UNAME: no unfettered ssh access" return 0 } #DIFFSYNC: validate_command miniade_validate_command() { [ $# -ge 1 ] || miniade_internal "miniade_validate_command: bad arg count ($#)" local CMDLINES CMDLINE CMDLINES=( "$@" ) for CMDLINE in "${CMDLINES[@]}"; do if ! type -p ${CMDLINE%% *} > /dev/null; then miniade_error "${CMDLINE%% *}: command not found" fi done return 0 } ######################################################################## # # Public functions: access module-private variable # ######################################################################## #DIFFSYNC: get_verboselevel miniade_get_verboselevel() { local VERBOSELEVEL_REF [ $# = 1 ] || miniade_internal "miniade_get_verboselevel: bad arg count ($#)" VERBOSELEVEL_REF=$1 eval "$VERBOSELEVEL_REF=$_MINIADE_VERBOSELEVEL" } #DIFFSYNC: set_verboselevel miniade_set_verboselevel() { [ $# = 1 ] || miniade_internal "miniade_set_verboselevel: bad arg count ($#)" _MINIADE_VERBOSELEVEL=$1 } #DIFFSYNC: get_simulate miniade_get_simulate() { local SIMULATE_REF [ $# = 1 ] || miniade_internal "miniade_get_simulate: bad arg count ($#)" SIMULATE_REF=$1 eval "$SIMULATE_REF=$_MINIADE_SIMULATE" } #DIFFSYNC: set_simulate miniade_set_simulate() { [ $# = 1 ] || miniade_internal "miniade_set_simulate: bad arg count ($#)" _MINIADE_SIMULATE=$1 } #DIFFSYNC: get_progname miniade_get_progname() { local PROGNAME_REF [ $# = 1 ] || miniade_internal "miniade_get_progname: bad arg count ($#)" PROGNAME_REF=$1 eval "$PROGNAME_REF=$_MINIADE_PROGNAME" } #DIFFSYNC: set_progname miniade_set_progname() { [ $# = 1 ] || miniade_internal "miniade_set_progname: bad arg count ($#)" _MINIADE_PROGNAME=$1 } ######################################################################## # # Module-private functions # ######################################################################## #DIFFSYNC: _display_message _miniade_display_message() { local TEXT LEVEL SYSLOGLEVEL WRITERFUNC_REF [ $# = 3 ] || miniade_internal "_miniade_display_message: bad argument count ($#)" TEXT="$1" LEVEL="$2" SYSLOGLEVEL="$3" for WRITERFUNC_REF in "${_MINIADE_DISPLAY_CALLBACK_REFS[@]}"; do $WRITERFUNC_REF "$TEXT" $LEVEL $SYSLOGLEVEL done } #DIFFSYNC: _display_message_stderr _miniade_display_message_stderr() { local TEXT LEVEL SYSLOGLEVEL TEXT="$1" LEVEL="$2" SYSLOGLEVEL="$3" if [ $_MINIADE_VERBOSELEVEL -lt $LEVEL ]; then return fi echo "$_MINIADE_PROGNAME: $TEXT" >&2 } #DIFFSYNC: _display_message_syslog _miniade_display_message_syslog() { local TEXT LEVEL SYSLOGLEVEL TEXT="$1" LEVEL="$2" SYSLOGLEVEL="$3" if [ $_MINIADE_VERBOSELEVEL -lt $LEVEL ]; then return fi logger -i -t $_MINIADE_PROGNAME -p local0.$SYSLOGLEVEL "$TEXT" } #DIFFSYNC: _display_message_log_file _miniade_display_message_log_file() { local TEXT LEVEL SYSLOGLEVEL TEXT="$1" LEVEL="$2" SYSLOGLEVEL="$3" # Note the absence if a 'if verbosel enough' filter here; i.e. # *all* messages are logged (if this function is on the the # writer callback refs list). echo "$_MINIADE_PROGNAME: $TEXT" >> /tmp/$_MINIADE_PROGNAME.$$.log } #DIFFSYNC: _display_message_dev_null _miniade_display_message_dev_null() { local TEXT LEVEL SYSLOGLEVEL TEXT="$1" LEVEL="$2" SYSLOGLEVEL="$3" } #DIFFSYNC: _replace_function _miniade_replace_function() { local OLD_FNCREF NEW_FNCREF [ $# = 2 ] || miniade_internal "_miniade_replace_function: bad argument count ($#)" OLD_FNCREF=$1 NEW_FNCREF=$2 eval "$OLD_FNCREF() { miniade_warning \"$OLD_FNCREF() is obsolete; switch to $NEW_FNCREF()\" $NEW_FNCREF \"\$@\" }" } #DIFFSYNC: _help_fallback _miniade_help_fallback() { local PROGNAME miniade_get_progname PROGNAME echo "Usage: $PROGNAME [ ]" exit 0 } #DIFFSYNC: _version_fallback _miniade_version_fallback() { local PROGNAME miniade_get_progname PROGNAME echo "$PROGNAME version 0" exit 0 } #DIFFSYNC: _paths_fallback _miniade_paths_fallback() { exit 0 } #DIFFSYNC: _special_options_fallback _miniade_special_options_fallback() { return 1 } #DIFFSYNC: _initialise _miniade_initialise() { # Function renames. _miniade_replace_function process_standard_options miniade_process_options _miniade_replace_function miniade_process_standard_options miniade_process_options _miniade_replace_function internal miniade_internal _miniade_replace_function error miniade_error _miniade_replace_function warning miniade_warning _miniade_replace_function info miniade_info _miniade_replace_function debug miniade_debug _miniade_replace_function evaler miniade_evaler _miniade_replace_function check_ssh_ok miniade_check_ssh_ok _miniade_replace_function lock miniade_lock _miniade_replace_function unlock miniade_unlock _miniade_replace_function lock_is_fresh miniade_lock_is_fresh _miniade_replace_function get_verboselevel miniade_get_verboselevel _miniade_replace_function set_verboselevel miniade_set_verboselevel _miniade_replace_function get_simulate miniade_get_simulate _miniade_replace_function set_simulate miniade_set_simulate _miniade_replace_function get_progname miniade_get_progname _miniade_replace_function set_progname miniade_set_progname _miniade_replace_function extra_shift miniade_extra_shift } ######################################################################## # # Public variables # ######################################################################## # (none) ######################################################################## # # Module-private variables with public access functions # ######################################################################## _MINIADE_PROGNAME=${0##*/} _MINIADE_VERBOSELEVEL=2 _MINIADE_SIMULATE=false _MINIADE_DISPLAY_CALLBACK_REFS=( _miniade_display_message_stderr ) ######################################################################## # # Other module-private variables # ######################################################################## ######################################################################## # # Actual code # ######################################################################## _miniade_initialise