# $HeadURL$ $LastChangedRevision$ ######################################################################## # # Modules # ######################################################################## ######################################################################## # # Characteristics of this module # ######################################################################## # (not needed in bash) ######################################################################## # # Public functions # ######################################################################## ######################################################################## # # Public functions: entry point related # ######################################################################## #DIFFSYNC: main ade_main() { local APP_MAIN_REF=$1; shift # If ERRSTACK is declared without [] then, even though there is no problem # accessing it as an array, it is pre-populated with one (empty) element, # which screws the element count. local -a ERRSTACK local RC ERRSTACK_REF ERRSTACK_REF=ERRSTACK # Initialise a stack. Other stack related attributes (e.g. dumpall, # verbosity level) are initialised as module-private data at the top # of this file. ade_set_messaging_parameters "$ERRSTACK_REF" "stack=$ERRSTACK_REF" # This is a one-time loop! It allows us to jump forward on error. while :; do # Register the options ADE will handle and the functions to handle them. ade_register_options "$ERRSTACK_REF" -o Vd:vhpn --longoptions=version,debug:,verbose,help,paths,simulate --callback-template="_ade_handle_option_%s" RC=$? [ $RC = $ADE_OK ] || break # Call the application main(). $APP_MAIN_REF "$ERRSTACK_REF" "$@" RC=$? [ $RC = $ADE_OK ] || break # This break makes it a one-time loop. break done # If necessary, display the error stack. if [ $RC != $ADE_OK ]; then ade_display_error_stack "$ERRSTACK_REF" ade_set_messaging_parameters "$ERRSTACK_REF" "stack=$ERRSTACK_REF" fi # Convert $rc into a Unix exit code and exit. _ade_exit "$ERRSTACK_REF" $RC } ######################################################################## # # Public functions: error stack related # ######################################################################## #DIFFSYNC: register_error_types ade_register_error_types() { local DEFINED_ERRORS_HASH_REF="$1" # Currently there are no registration-time checks or actions other than # the registration. We *could* check that the reference refers to a non-empty # hash, or we could even copy the errors out of it, so that it becomes impossible # to add or delete errors to the set of defined errors *after* registration. # # Append the reference to the array of registered hash refs. Note that there # is no nice 'push' in bash, so we need to say the equivalent of the perl # '$x[$#x] = ...' _ADE_REGISTERED_DEFINED_ERRORS_HASH_REFS_ARRAY[${#_ADE_REGISTERED_DEFINED_ERRORS_HASH_REFS_ARRAY[*]}]=$DEFINED_ERRORS_HASH_REF return $ADE_OK } #DIFFSYNC: display_error_stack ade_display_error_stack() { local ERRSTACK_REF DISPLAYFUNC_REF DEBUG_LEVEL local I J ERRPARS_REF FRAMECOUNT ERRPARS DEFINED_ERROR KEY FMT PAR ERROR_MESSAGE DEFINITION_FRAME ERROR_FRAME PARS ENCODED_PARS ERRSTACK_REF="$1" DISPLAYFUNC_REF="$2" DEBUG_LEVEL="$3" # # Check/sanitize parameters. # if [ "X$DISPLAYFUNC_REF" = X ]; then DISPLAYFUNC_REF=_ade_error fi # # For each error frame on the stack ... # eval "FRAMECOUNT=\${#$ERRSTACK_REF[*]}" for ((I=0; $I<$FRAMECOUNT; I=$(($I+1)))); do # Skip all but the top frame if requested so to do [ $I = 0 ] || $_ADE_DUMP_WHOLE_ERROR_STACK_FLAG || continue # Retrieve the frame from the referenced stack. The frame will # look something like "KEY=ADE_ERR_MISC; ENCODED_PARS=(foo bar)" eval "ERROR_FRAME=\${$ERRSTACK_REF[\$I]}" # Split the occurrence frame into ERR (the key to the defined errors hash, which we # will look up in the definitions hash shortly to get the format) and ENCODED_PARS (the # parameters to this particular occurrence of that error) eval "$ERROR_FRAME" # Now look up the error code to get the definition frame. _ade_validate_error_key "$ERRSTACK_REF" $KEY DEFINITION_FRAME || return $? # Now split the definition frame into KEY (the key) and the FMT (the format string). eval "$DEFINITION_FRAME" # Unpack ENCODED_PARS[] into PARS[] unset PARS for ((J=0; $J<${#ENCODED_PARS[*]}; J++)); do ade_decode_z "$ERRSTACK_REF" "${ENCODED_PARS[$J]}" PARS[$J] || return $? done # # Expand the description's format with the parameters in the same stack frame # # Shell printf is a bit funny in that even if there are not enough '%s' in the # the format, all arguments will be consumed and outputted. This will be fine # as long as the right number of parameters is provided at error-time, but # if not then expect odd results. printf -v ERROR_MESSAGE "$FMT" "${PARS[@]}" # # Display the error # if $_ADE_DUMP_WHOLE_ERROR_STACK_FLAG; then $DISPLAYFUNC_REF "$ERRSTACK_REF" "frame#$I: $ERROR_MESSAGE" else $DISPLAYFUNC_REF "$ERRSTACK_REF" "$ERROR_MESSAGE" fi done # # If we get this far all is ok. # return $ADE_OK } ######################################################################## # # Public functions: string/stream manipulation functions # ######################################################################## #DIFFSYNC: encode_z ade_encode_z() { local ERRSTACK_REF="$1"; shift local SRC="$1"; shift local DST_REF="$1"; shift local SAFE_CHARS="$1"; shift local DANGEROUS_CHARS="$1"; shift local OCHR # For debugging purposes only SRC2="$SRC" eval "$DST_REF=" while [[ $SRC =~ . ]]; do [[ $SRC =~ (.)(.*) ]] OCHR="${BASH_REMATCH[1]}" SRC="${BASH_REMATCH[2]}" if [ "X$SAFE_CHARS" != X ] && eval "[[ \$OCHR =~ [${SAFE_CHARS/ /\\ }] ]]"; then : elif [ "X$DANGEROUS_CHARS" != X ] && eval "[[ \$OCHR =~ [${DANGEROUS_CHARS/ /\\ }] ]]"; then OCHR=$(printf "Z%02X" "'$OCHR") elif ! [[ $OCHR =~ [A-Ya-z0-9] ]]; then OCHR=$(printf "Z%02X" "'$OCHR") fi eval "$DST_REF+=\"\$OCHR\"" done return $ADE_OK } #DIFFSYNC: decode_z ade_decode_z() { local ERRSTACK_REF="$1"; shift local SRC="$1"; shift local DST_REF="$1"; shift local OCHR # For debugging purposes only SRC2="$SRC" eval "$DST_REF=" while [[ $SRC =~ . ]]; do [[ $SRC =~ (Z..|[^Z])(.*) ]] OCHR="${BASH_REMATCH[1]}" SRC="${BASH_REMATCH[2]}" ! [[ $OCHR =~ Z(..) ]] || OCHR="$(echo -e "\x${BASH_REMATCH[1]}")" eval "$DST_REF+=\"\$OCHR\"" done return $ADE_OK } #DIFFSYNC: extract_version ade_extract_version() { local ERRSTACK_REF="$1"; shift local SVNSTRING="$1"; shift local VERSION_REF="$1"; shift local BASH_REMATCH_1_SAVED RE # Unquoted RHS with escaped spaces is workaround for BTS#487387. RE='^\$HeadURL: .*?/(trunk|tags/([^/]+)|branches/([^/]+))/.*?\$.* \$LastChangedRevision: ([0-9]+) \$$' if ! [[ $SVNSTRING =~ $RE ]]; then ade_internal "$ERRSTACK_REF" "ade_extract_version: failed to match" fi ade_debug "$ERRSTACK_REF" 50 "ade_extract_version: SVNSTRING='$SVNSTRING', RE='$RE', BASH_REMATCH[1]=${BASH_REMATCH[1]}, BASH_REMATCH[2]=${BASH_REMATCH[2]}, BASH_REMATCH[3]=${BASH_REMATCH[3]}, BASH_REMATCH[4]=${BASH_REMATCH[4]}" # In the Perl version of this function, the rematch array ($1, $2, $3, ...) is not # overwritten by subsequent match attempts *which fail*. But it seems in the bash # version that at least the first element of the array (${BASH_REMATCH[1]}) is # overwritten. So we need to save at least the first setting. BASH_REMATCH_1_SAVED=${BASH_REMATCH[1]} if [ "$BASH_REMATCH_1_SAVED" = trunk ]; then eval "$VERSION_REF=\"svn/trunk/${BASH_REMATCH[4]}\"" elif [[ "$BASH_REMATCH_1_SAVED" =~ ^tags/(.*) ]]; then eval "$VERSION_REF=\"${BASH_REMATCH[1]}\"" elif [[ "$BASH_REMATCH_1_SAVED" =~ ^branches/(.*) ]]; then eval "$VERSION_REF=\"svn/branch/${BASH_REMATCH[1]}\"" else ade_error "$ERRSTACK_REF" ADE_ERR_NOTIMPLEMENTED "support for other version strings" return $ADE_FAIL fi return $ADE_OK } #DIFFSYNC: split_string ade_split_string() { local ERRSTACK_REF="$1"; shift local STRING SEP VARS I REGEXP STRING="$1" SEP="$2" shift 2 VARS=( $* ) REGEXP="^(.*)" for ((I=1; I<${#VARS[*]}; I++)); do REGEXP="${REGEXP}$SEP(.*)" done REGEXP="$REGEXP\$" [[ $STRING =~ $REGEXP ]] || { ade_error "$ERRSTACK_REF" ADE_ERR_MISC "string '$STRING' did not match '$REGEXP'" return $ADE_FAIL } for ((I=0; $I<${#VARS[*]}; I++)); do eval "${VARS[$I]}=\"\${BASH_REMATCH[$((I+1))]}\"" done return $ADE_OK } #DIFFSYNC: validate_regexp # (not implemented) #DIFFSYNC: upper_case ade_upper_case() { local ERRSTACK_REF="$1"; shift local STRING STRING_UC_REF # Process arguments [ $# = 2 ] || ade_internal "$ERRSTACK_REF" "ade_upper_case: bad arg count ($#)" STRING="$1" STRING_UC_REF="$2" # Guts eval "$STRING_UC_REF=\"\${STRING^^}\"" return $ADE_OK } #DIFFSYNC: lower_case ade_lower_case() { local ERRSTACK_REF="$1"; shift local STRING STRING_LC_REF # Process arguments [ $# = 2 ] || ade_internal "$ERRSTACK_REF" "ade_upper_case: bad arg count ($#)" STRING="$1" STRING_LC_REF="$2" # Guts eval "$STRING_LC_REF=\"\${STRING,,}\"" return $ADE_OK } ######################################################################## # # Public functions: messaging functions # ######################################################################## #DIFFSYNC: internal ade_internal() { local ERRSTACK_REF="$1"; shift local MESSAGE="$1"; shift # The error frames on the stack may be relevant to solving the # problem, so dump them. ade_display_error_stack "$ERRSTACK_REF" _ade_internal "$ERRSTACK_REF" "$MESSAGE" # Previously, we can exited here using _ade_exit(), but that # can call registered cleanup functions that themselves generate # internal errors, which leads to infinite recursion. Therefore, # we simply exit. This is also more sensible: an internal error # should result in the system exiting immediately, without any # attempt to clean up. exit 5 } #DIFFSYNC: error ade_error() { local ERRSTACK_REF="$1"; shift local DEFINED_ERRORS_HASH_KEY="$1"; shift local ERROR_FRAME J local -a PARS ENCODED_PARS PARS=("$@") # Deliberately no third parameter as we don't care about getting the # definition frame at the moment. _ade_validate_error_key "$ERRSTACK_REF" "$DEFINED_ERRORS_HASH_KEY" || return $? # The quotes in the parameters make it difficult to build a structure that # contain the parameters in a record which has also other fields (i.e the # error code and then the array of error parameters). For this reason we # Z-encode the parameters making them less difficult to pack, and actually, # if Z-encode spaces too, then we don't even need to quote anything! ENCODED_PARS=() # WITH THIS AND ANOTHER BLOCK ELSEWHERE COMMENTED OUT IT WORKS. for ((J=0; $J<${#PARS[*]}; J++)); do ade_encode_z "$ERRSTACK_REF" "${PARS[$J]}" "ENCODED_PARS[$J]" "" "" || return $? done # Create the error frame. ERROR_FRAME="KEY=$DEFINED_ERRORS_HASH_KEY; ENCODED_PARS=(${ENCODED_PARS[@]})" # Push it on the error stack. eval "$ERRSTACK_REF[\${#$ERRSTACK_REF[*]}]=\"$ERROR_FRAME\"" return $ADE_OK } #DIFFSYNC: warning ade_warning() { local ERRSTACK_REF="$1"; shift local DEFINED_ERRORS_HASH_KEY="$1"; shift local ERROR_FRAME ENCODED_PARS J PARS PARS=("$@") # Deliberately no third parameter as we don't care about getting the # definition frame at the moment. _ade_validate_error_key "$ERRSTACK_REF" "$DEFINED_ERRORS_HASH_KEY" || return $? # The quotes in the parameters make it difficult to build a structure that # contain the parameters in a record which has also other fields (i.e the # error code and then the array of error parameters). For this reason we # Z-encode the parameters making them less difficult to pack, and actually, # if Z-encode spaces too, then we don't even need to quote anything! unset ENCODED_PARS for ((J=0; $J<${#PARS[*]}; J++)); do ade_encode_z "$ERRSTACK_REF" "${PARS[$J]}" ENCODED_PARS[$J] "" "" || return $? done # Create the error frame. ERROR_FRAME="KEY=$DEFINED_ERRORS_HASH_KEY; ENCODED_PARS=(${ENCODED_PARS[@]})" # Push it on the error stack. eval "$ERRSTACK_REF[\${#$ERRSTACK_REF[*]}]=\"$ERROR_FRAME\"" # Do an immediate stack dump using _ade_warning() to display the # frames and reset the stack. ade_display_error_stack "$ERRSTACK_REF" _ade_warning || return $? ade_set_messaging_parameters "$ERRSTACK_REF" "stack=$ERRSTACK_REF" return $ADE_OK } #DIFFSYNC: info ade_info() { local ERRSTACK_REF MESSAGE ERRSTACK_REF="$1" MESSAGE="$2" _ade_info "$ERRSTACK_REF" "$MESSAGE" } #DIFFSYNC: debug ade_debug() { local ERRSTACK_REF LEVEL MESSAGE ERRSTACK_REF="$1" LEVEL="$2" MESSAGE="$3" _ade_debug "$ERRSTACK_REF" "$LEVEL" "$MESSAGE" } #DIFFSYNC: show_help ade_show_help() { local ERRSTACK_REF="$1"; shift local USAGE_SHORT_TEXT USAGE_LONG_TEXT PROGNAME # Call callbacks to get text into USAGE_SHORT_TEXT and USAGE_LONG_TEXT. $_ADE_USAGE_CALLBACK_REF "$ERRSTACK_REF" USAGE_SHORT_TEXT USAGE_LONG_TEXT || return $? ade_get_progname "$ERRSTACK_REF" PROGNAME echo "Usage: $PROGNAME [ ] $USAGE_SHORT_TEXT" echo echo "Options: -V | --version display program version" echo " -v | --verbose verbose" echo " -d | --debug= set debug level" echo " -h | --help display this text" echo " -p | --paths list used paths" echo " -n | --simulate simulate (limited effect!)" # Display received text, but flatten trailing-but-not-leading whitespace. [ "X$USAGE_LONG_TEXT" = X ] || echo "$USAGE_LONG_TEXT" | sed -e 's/[ \t]*$//' echo _ade_exit "$ERRSTACK_REF" 0 } #DIFFSYNC: show_bad_usage ade_show_bad_usage() { local ERRSTACK_REF="$1"; shift local PROGNAME ade_get_progname "$ERRSTACK_REF" PROGNAME echo "$PROGNAME: ERROR: type '$PROGNAME --help' for correct usage." >&2 _ade_exit "$ERRSTACK_REF" 1 } #DIFFSYNC: show_version ade_show_version() { local ERRSTACK_REF="$1"; shift local VERSION_TEXT # Call callback to get text into VERSION_TEXT $_ADE_VERSION_CALLBACK_REF "$ERRSTACK_REF" VERSION_TEXT || return $? echo "$_ADE_PROGNAME version $VERSION_TEXT" # We do not return from this function _ade_exit "$ERRSTACK_REF" 0 } #DIFFSYNC: show_paths ade_show_paths() { local ERRSTACK_REF="$1"; shift local LISTPATHS_TEXT UNIX_RC UNIX_RC=0 # Call callback to get text into LISTPATHS_TEXT RC=$ADE_OK; $_ADE_LISTPATHS_CALLBACK_REF "$ERRSTACK_REF" LISTPATHS_TEXT || RC=$? if [ $RC != $ADE_OK ]; then ade_display_error_stack "$ERRSTACK_REF" ade_set_messaging_parameters "$ERRSTACK_REF" "stack=$ERRSTACK_REF" UNIX_RC=1 fi # Display received text, but flatten whitespace and change equals to colons. echo "$LISTPATHS_TEXT" | sed -e 's/^[ \t]*//' -e 's/[ \t]*$//' -e '/^$/d' -e 's/=/: /' # We do not return from this function _ade_exit "$ERRSTACK_REF" $UNIX_RC } #DIFFSYNC: ask_question ade_ask_question() { local ERRSTACK_REF="$1"; shift local HINT="$1"; shift local PROMPT="$1"; shift local DEFAULT="$1"; shift local VALIDATE_FNC="$1"; shift local RATIONALISE_FNC="$1"; shift local RATIONALISED_RESPONSE_REF="$1"; shift local RESPONSE RC VALIDATED if [ "X$HINT" != X ]; then ade_debug "$ERRSTACK_REF" 20 "ade_ask_question: hint is defined; displaying ..." echo -e "$HINT" fi while :; do [ ! -t 0 ] || echo -n "$_ADE_PROGNAME: QUESTION: $PROMPT [$DEFAULT]: "; read RESPONSE || { ade_error "$ERRSTACK_REF" ADE_ERR_EOF "stdin" return $ADE_FAIL } [ -t 0 ] || echo [ "X$RESPONSE" != X ] || RESPONSE="$DEFAULT" [ "X$RESPONSE" != X. ] || RESPONSE="" ade_debug "$ERRSTACK_REF" 20 "ade_ask_question: RESPONSE=[$RESPONSE]" $VALIDATE_FNC "$ERRSTACK_REF" "$RESPONSE" VALIDATED || return $? ! $VALIDATED || break ade_error "$ERRSTACK_REF" ADE_ERR_MISC "$RESPONSE: invalid selection" ade_display_error_stack "$ERRSTACK_REF" ade_set_messaging_parameters "$ERRSTACK_REF" "stack=$ERRSTACK_REF" done if [ "X$RATIONALISE_FNC" = X ]; then ade_debug "$ERRSTACK_REF" 20 "ade_ask_question: rationalisation function undefined" eval "$RATIONALISED_RESPONSE_REF=\"$RESPONSE\"" return $ADE_OK fi $RATIONALISE_FNC "$ERRSTACK_REF" "$RESPONSE" $RATIONALISED_RESPONSE_REF || { RC=$? ade_debug "$ERRSTACK_REF" 20 "ade_ask_question: rationalisation function failed" return $? } ade_debug "$ERRSTACK_REF" 20 "ade_ask_question: rationalisation function succeeded" return $ADE_OK } ######################################################################## # # Public functions: filename manipulation functions # ######################################################################## #DIFFSYNC: get_absolute_path ade_get_absolute_path() { local ERRSTACK_REF="$1"; shift local WHAT="$1" local CWD="$2" local NEW_WHAT_REF="$3" local BITS NEW_BITS NEW_WHAT I # If path is not / but ends with / then trim that. [ "X$WHAT" = X/ ] || WHAT=${WHAT%/} # If not absolute prepend current directory. (Consulting $PWD is closest to # what a bash user would expect.) [ "X$CWD" != X ] || CWD=$PWD [[ $WHAT =~ ^/ ]] || WHAT=$CWD/$WHAT # Discard leading / first (perl version does this inside processing loop but it's # easier to ditch it early). WHAT=${WHAT#/} ade_debug "$ERRSTACK_REF" 10 "ade_get_absolute_path: after rationalisation: WHAT=$WHAT" # Chop up remainder. BITS=() while [[ $WHAT =~ ^([^/]*)/(.*)$ ]]; do _ade_push "$ERRSTACK_REF" BITS "${BASH_REMATCH[1]}" WHAT="${BASH_REMATCH[2]}" done _ade_push "$ERRSTACK_REF" BITS "$WHAT" _ade_join "$ERRSTACK_REF" "] [" R "${BITS[@]}" ade_debug "$ERRSTACK_REF" 10 "ade_get_absolute_path: after chopping: BITS=[$R]" # Process bits in sequence. NEW_BITS=() for ((I=0; I<${#BITS[*]}; ++I)); do if [ "X${BITS[$I]}" = X.. ]; then # If in subdir then .. takes us out. But if already at top then .. does # nothing, it is not an error (i.e. "cd /.." works). if [ ${#NEW_BITS[*]} -gt 0 ]; then _ade_pop "$ERRSTACK_REF" NEW_BITS fi # Double slash would give empty bit. elif [ "X${BITS[$I]}" = X ]; then :; # '.' gives empty bit. elif [ "X${BITS[$I]}" = X. ]; then :; else _ade_push "$ERRSTACK_REF" NEW_BITS "${BITS[$I]}" fi done _ade_join "$ERRSTACK_REF" ", " R "${NEW_BITS[@]}" ade_debug "$ERRSTACK_REF" 10 "ade_get_absolute_path: after chopping: NEW_BITS=[ $R ]" # Remember above we said that we're working with the path *relative* to / (because # we discard the leading '/') so now we need to prepend it back on again. _ade_join "$ERRSTACK_REF" "/" NEW_WHAT "${NEW_BITS[@]}" NEW_WHAT="/$NEW_WHAT" ade_debug "$ERRSTACK_REF" 10 "ade_get_absolute_path: after reassembly: NEW_WHAT=$NEW_WHAT" eval "$NEW_WHAT_REF=\"\$NEW_WHAT\"" return $ADE_OK } #DIFFSYNC: check_readable # (not implemented) #DIFFSYNC: check_writable # (not implemented) ######################################################################## # # Public functions: file handle manipulation functions # ######################################################################## #DIFFSYNC: get_free_filehandle ade_get_free_filehandle() { local ERRSTACK_REF="$1"; shift local FD_REF LOCAL_FD # Process arguments FD_REF=$1 ade_debug "$ERRSTACK_REF" 10 "ade_get_free_filehandle: HANDLE_REF=$FD_REF" LOCAL_FD=0 for ((LOCAL_FD=0; LOCAL_FD<256; LOCAL_FD++)); do [ -e /proc/$$/fd/$LOCAL_FD ] || break done if [ $LOCAL_FD = 256 ]; then ade_error "$ERRSTACK_REF" PAA_ERR_MISC "out of file descriptors" return $ADE_FAIL fi eval "$FD_REF=$LOCAL_FD" return $ADE_OK } #DIFFSYNC: open_string_for_reading ade_open_string_for_reading() { local ERRSTACK_REF="$1"; shift local FD_REF FILE # Process arguments [ $# = 2 ] || ade_internal "$ERRSTACK_REF" "ade_open_string_for_reading: arg count ($#)" FD_REF=$1 STRING="$2" ade_debug "$ERRSTACK_REF" 10 "ade_open_string_for_reading: FD_REF=$FD_REF, STRING=$STRING" ade_create_filehandle "$ERRSTACK_REF" $FD_REF "<<<\"$STRING\"" || return $? return $ADE_OK } #DIFFSYNC: create_filehandle ade_create_filehandle() { local ERRSTACK_REF="$1"; shift local FD_REF REDIR_AND_THING # Process arguments [ $# = 2 ] || ade_internal "$ERRSTACK_REF" "ade_create_filehandle: arg count ($#)" FD_REF="$1" REDIR_AND_THING="$2" ade_debug "$ERRSTACK_REF" 10 "ade_create_filehandle: FD_REF=$FD_REF, REDIR_AND_THING=$REDIR_AND_THING" # Guts # bash 4 has {VAR} <&-' and 'exec >&-' both close the file, regardless of # whether the file has been opened for writing or reading. So to save # having to know, I've just picked one of these ways and used it. # bash 4 has {VAR} "$FILE" return $ADE_OK } #DIFFSYNC: validate_uuid ade_validate_uuid() { local ERRSTACK_REF="$1"; shift local UUID [ $# = 1 ] || ade_internal "$ERRSTACK_REF" "ade_validate_uuid: bad arg count ($#)" UUID="$1" if ! [[ $UUID =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]; then ade_error "$ERRSTACK_REF" ADE_ERR_MISC "$UUID: invalid uuid" return $ADE_FAIL fi return $ADE_OK } ######################################################################## # # Public functions: file content manipulation functions # ######################################################################## #DIFFSYNC: open_compressed_file_for_reading # (not implemented) #DIFFSYNC: open_compressed_file_for_writing # (not implemented) #DIFFSYNC: get_md5sum # (not implemented) ######################################################################## # # Public functions: option processing # ######################################################################## #DIFFSYNC: register_options ade_register_options() { local ERRSTACK_REF OPT_FUNC_TEMPLATE SHORT_OPTS LONG_OPTS ERRSTACK_REF="$1"; shift OPT_FUNC_TEMPLATE="option_handler_%s" # This function tries to support the same options as the regular getopt command # supports, even if it doesn't do the same thing. It also needs to support # an option to specify the functions to be called to actually process options # (although the processing itself is not done by calling this function). The # most succinct way to this is to provide a template which will be expanded to # produce the name of a function that will actually process the option. The template # text should include '%s' which will be changed to the particular option being # registered. while [ $# != 0 ]; do case $1 in -o) SHORT_OPTS=$2; shift ;; --longoptions=*) LONG_OPTS=${1#*=} ;; --callback-template=*) OPT_FUNC_TEMPLATE=${1#*=} ;; esac shift done ade_debug "$ERRSTACK_REF" 10 "ade_register_options: SHORT_OPTS=$SHORT_OPTS, LONG_OPTS=$LONG_OPTS" # Register the shorts and then the longs for OPT_TYPE in short long; do case $OPT_TYPE in short) OPTS="$SHORT_OPTS" RE='^([a-zA-Z])(:?)(.*)$' ;; long) OPTS="$LONG_OPTS" RE='^([a-zA-Z][-a-zA-Z0-9]+)(:?),?(.*)$' ;; esac while [[ $OPTS =~ $RE ]]; do OPT=${BASH_REMATCH[1]} OPT_SUFF=${OPT//-/_} OPT_ARGC=${#BASH_REMATCH[2]} OPT_FUNC=${OPT_FUNC_TEMPLATE/\%s/$OPT_SUFF} # Remaining opts are here OPTS=${BASH_REMATCH[3]} ade_debug "$ERRSTACK_REF" 10 "ade_register_options: OPT=$OPT, OPT_SUFF=$OPT_SUFF, OPT_ARGC=$OPT_ARGC, OPT_FUNC=$OPT_FUNC, OPTS=$OPTS" [ "X$(type -t $OPT_FUNC)" = Xfunction ] || { ade_error "$ERRSTACK_REF" ADE_ERR_UNDEFINED $OPT_FUNC "function"; return $ADE_FAIL; } ade_debug "$ERRSTACK_REF" 10 "ade_register_options: registering callback $OPT_FUNC for $OPT_TYPE option '$OPT' ..." # Map to (, , ) # (The will help when constructing the list of accepted options in # the format that the normal getopt command wants - i.e. or ). # Bash version 3 doesn't support hashes so use "eval VAR_SUFFIX" trick. eval "ADE_REGISTERED_OPT_$OPT_SUFF=($OPT_TYPE $OPT_FUNC $OPT_ARGC)" # In addition, store the short option in an array with a fixed name, # this will make it easier to process later. _ADE_REGISTERED_OPTS[${#_ADE_REGISTERED_OPTS[*]}]=$OPT done done return $ADE_OK } #DIFFSYNC: process_options ade_process_options() { local ERRSTACK_REF NEW_DOLLAR_AT_REF OPT OPT_SUFF OPT_TYPE OPT_FUNC OPT_ARGC SHORT_OPTS LONG_OPTS REORDERED_DOLLAR_AT FOUND ERRSTACK_REF="$1"; shift NEW_DOLLAR_AT_REF=$1; shift # Collect all the short options and long options together in format suitable for getopts. for OPT in ${_ADE_REGISTERED_OPTS[*]}; do OPT_SUFF=${OPT//-/_} eval "OPT_TYPE=\${ADE_REGISTERED_OPT_$OPT_SUFF[0]}" eval "OPT_FUNC=\${ADE_REGISTERED_OPT_$OPT_SUFF[1]}" eval "OPT_ARGC=\${ADE_REGISTERED_OPT_$OPT_SUFF[2]}" if [ $OPT_TYPE = short -a $OPT_ARGC = 0 ]; then SHORT_OPTS="$SHORT_OPTS$OPT" elif [ $OPT_TYPE = short -a $OPT_ARGC = 1 ]; then SHORT_OPTS="$SHORT_OPTS$OPT:" elif [ $OPT_TYPE = long -a $OPT_ARGC = 0 ]; then LONG_OPTS="$LONG_OPTS${LONG_OPTS:+,}$OPT" elif [ $OPT_TYPE = long -a $OPT_ARGC = 1 ]; then LONG_OPTS="$LONG_OPTS${LONG_OPTS:+,}$OPT:" else ade_debug "$ERRSTACK_REF" 10 "ade_process_options: internal error" fi done ade_debug "$ERRSTACK_REF" 10 "ade_process_options: after assembling the short options into a single word for getopt we have: SHORT_OPTS=$SHORT_OPTS" ade_debug "$ERRSTACK_REF" 10 "ade_process_options: after assembling the long options into a single word for getopt we have: LONG_OPTS=$LONG_OPTS" # Call getopt to split options ade_debug "$ERRSTACK_REF" 10 "ade_process_options: before calling getopt to split options, \$@ is ""$@" if ! REORDERED_DOLLAR_AT=$(POSIXLY_CORRECT=1 getopt -q -o $SHORT_OPTS --longoptions $LONG_OPTS -- "$@"); then ade_show_bad_usage "$ERRSTACK_REF" fi eval "set -- $REORDERED_DOLLAR_AT" ade_debug "$ERRSTACK_REF" 10 "ade_process_options: after calling getopt to split options, \$@ is ""$@" # Now actually process the options while [ $# != 0 ]; do ade_debug "$ERRSTACK_REF" 10 "ade_process_options: processing option $1 ..." case $1 in --) shift; break ;; -*) # Scan the list of short and long options for a match. FOUND=false for OPT in ${_ADE_REGISTERED_OPTS[*]}; do OPT_SUFF=${OPT//-/_} eval "OPT_TYPE=\${ADE_REGISTERED_OPT_$OPT_SUFF[0]}" eval "OPT_FUNC=\${ADE_REGISTERED_OPT_$OPT_SUFF[1]}" eval "OPT_ARGC=\${ADE_REGISTERED_OPT_$OPT_SUFF[2]}" if [ $OPT_TYPE = short -a "X$1" = "X-$OPT" ]; then FOUND=true break elif [ $OPT_TYPE = long -a "X$1" = "X--$OPT" ]; then FOUND=true break fi done if ! $FOUND; then ade_debug "$ERRSTACK_REF" 10 "ade_process_options: handler for option '$1' not found (usage error?)" elif [ $OPT_ARGC = 0 ]; then ade_debug "$ERRSTACK_REF" 10 "ade_process_options: calling: $OPT_FUNC() ..." $OPT_FUNC "$ERRSTACK_REF" || return $? elif [ $OPT_ARGC = 1 ]; then ade_debug "$ERRSTACK_REF" 10 "ade_process_options: calling: $OPT_FUNC($2) ..." $OPT_FUNC "$ERRSTACK_REF" "$2" || return $? shift else ade_debug "$ERRSTACK_REF" 10 "ade_process_options: this should not happen (arg count was not zero or one!)" fi ;; *) break ;; esac shift done # Any remaining arguments need to be passed back to the caller (who # should use the 'eval set' trick to put them back into "$@"). eval "$NEW_DOLLAR_AT_REF=(\"\$@\")" # Display help, usage of version info (these functions will all exit). ade_debug "$ERRSTACK_REF" 10 "ade_process_options: _ADE_SHOW_PATHS_FLAG=$_ADE_SHOW_PATHS_FLAG, _ADE_SHOW_HELP_FLAG=$_ADE_SHOW_HELP_FLAG, _ADE_SHOW_VERSION_FLAG=$_ADE_SHOW_VERSION_FLAG" if $_ADE_SHOW_PATHS_FLAG; then ade_show_paths "$ERRSTACK_REF" ade_internal "$ERRSTACK_REF" "ade_process_options: ade_show_paths() unexpectedly returned (rc=$?)" fi if $_ADE_SHOW_HELP_FLAG ; then ade_show_help "$ERRSTACK_REF" ade_internal "$ERRSTACK_REF" "ade_process_options: ade_show_help() unexpectedly returned (rc=$?)" fi if $_ADE_SHOW_VERSION_FLAG; then ade_show_version "$ERRSTACK_REF" ade_internal "$ERRSTACK_REF" "ade_process_options: ade_show_version() unexpectedly returned (rc=$?)" fi return $ADE_OK } #DIFFSYNC: funcname_to_funcref # (not implemented) ######################################################################## # # Public functions: special functions # ######################################################################## #DIFFSYNC: manage_rolling_status # (not implemented) #DIFFSYNC: manage_cache ade_manage_cache() { local ERRSTACK_REF="$1"; shift local CACHE_FILE="$1" local CACHE_EXPIRY_PERIOD="$2" local DESIRED_CACHE_EXPIRY="$3" local NEW_CACHE_TESTER="$4" local NEW_CACHE_GETTER="$5" local CACHE_ID="$6" local ACTUAL_CACHE_EXPIRY CACHE_DIR REFRESH_CACHE # 'find -mtime +1' indicates cache expired if older # than *48* hours, not 24, which is probably what the # caller is expecting the cache expiry period to mean. CACHE_EXPIRY_PERIOD=$(expr $CACHE_EXPIRY_PERIOD - 1) # Determine if cache actually expired { [ -f $CACHE_FILE -a "X$(find $CACHE_FILE -mtime +$CACHE_EXPIRY_PERIOD -print 2>/dev/null)" = X ] && ACTUAL_CACHE_EXPIRY=false; } || ACTUAL_CACHE_EXPIRY=true # This is the logic for combining the desired cache expiry status and # the actual cache expiry status to determine if the cache should be # refreshed. # # DESIRED EXPIRY # Y N don't care # ----------+is-cache--+---------- # | | | | # A | | no | | # C | | update | | # T | | | | # U Y | update |no-cache--| update | # A | | | | # L | | update | | # | | | | # ----------+----------+---------- # E | | | | # X | | | | # P | | | | # I | | no | no | # R N | update | update | update | # Y | | | | # | | | | # | | | | # ----------+----------+---------- # # And now here it is in code, using the usual 'bite off the big # bits first' approach. if [ $DESIRED_CACHE_EXPIRY = true ]; then REFRESH_CACHE=true elif [ $ACTUAL_CACHE_EXPIRY = false ]; then REFRESH_CACHE=false elif [ $DESIRED_CACHE_EXPIRY = unset ]; then REFRESH_CACHE=true elif [ -f $CACHE_FILE ]; then REFRESH_CACHE=false else ade_warning "$ERRSTACK_REF" ADE_ERR_MISC "cache will be refreshed against user's desire because no cache file exists yet" REFRESH_CACHE=true fi ade_debug "$ERRSTACK_REF" 4 "ade_manage_cache: DESIRED_CACHE_EXPIRY=$DESIRED_CACHE_EXPIRY, ACTUAL_CACHE_EXPIRY=$ACTUAL_CACHE_EXPIRY, REFRESH_CACHE=$REFRESH_CACHE" if [ $REFRESH_CACHE = true ]; then CACHE_DIR=$(dirname $CACHE_FILE) # sanity check cache if [ -f $CACHE_FILE ]; then [ ! -w $CACHE_FILE ] && ade_error "$ERRSTACK_REF" ADE_ERR_ACCESS "$CACHE_FILE" "write" elif [ -d $CACHE_DIR ]; then [ ! -w $CACHE_DIR ] && ade_error "$ERRSTACK_REF" ADE_ERR_ACCESS "$CACHE_FILE" "create" else ade_error "$CACHE_DIR: cache file's directory does not exist" fi # update cache ade_info "$ERRSTACK_REF" "updating $CACHE_ID cache, please wait ... " ade_register_temp_file "$ERRSTACK_REF" /tmp/$_ADE_PROGNAME.$$.newcache ade_debug "$ERRSTACK_REF" 4 "ade_manage_cache: calling $NEW_CACHE_GETTER() ..." $NEW_CACHE_GETTER > /tmp/$_ADE_PROGNAME.$$.newcache # if tests pass, accept it. if $NEW_CACHE_TESTER /tmp/$_ADE_PROGNAME.$$.newcache; then cat /tmp/$_ADE_PROGNAME.$$.newcache > $CACHE_FILE rm /tmp/$_ADE_PROGNAME.$$.newcache ade_deregister_temp_file "$ERRSTACK_REF" /tmp/$_ADE_PROGNAME.$$.newcache # if tests fail but we have an old version, reject with warning elif [ -f $CACHE_FILE ]; then ade_deregister_temp_file "$ERRSTACK_REF" /tmp/$_ADE_PROGNAME.$$.newcache ade_warning "$ERRSTACK_REF" ADE_ERR_MISC "new $CACHE_ID cache is too small, using old cache (see /tmp/$_ADE_PROGNAME.$$.newcache for more information)" # if tests fail and we don't have an old version, reject with error else ade_deregister_temp_file "$ERRSTACK_REF" /tmp/$_ADE_PROGNAME.$$.newcache ade_error "new $CACHE_ID cache is too small (see /tmp/$_ADE_PROGNAME.$$.newcache for more information)" fi fi # Pass cache to parent cat $CACHE_FILE # success return code return $ADE_OK } #DIFFSYNC: fork_multi # (not implemented) ######################################################################## # # Public functions: user related # ######################################################################## #DIFFSYNC: get_my_group ade_get_my_group() { local ERRSTACK_REF="$1"; shift local FILE GROUPNAME FILE=/tmp/$_ADE_PROGNAME.$$.ade_get_my_group ade_register_temp_file "$ERRSTACK_REF" $FILE touch $FILE GROUPNAME=$(ls -ld $FILE | awk '{ print $4 }') rm -f $FILE ade_deregister_temp_file "$ERRSTACK_REF" $FILE echo "$GROUPNAME" return $ADE_OK } ######################################################################## # # Public functions: process management functions # ######################################################################## #DIFFSYNC: validate_command # Identical function in miniade ade_validate_command() { local ERRSTACK_REF="$1"; shift local CMDLINES CMDLINE CMDLINES=( "$@" ) for CMDLINE in "${CMDLINES[@]}"; do if ! type -p ${CMDLINE%% *} > /dev/null; then ade_error "$ERRSTACK_REF" ADE_ERR_MISC "${CMDLINE%% *}: command not found" return $ADE_FAIL fi done return $ADE_OK } #DIFFSYNC: start_coproc ade_start_coproc() { local ERRSTACK_REF="$1"; shift local HANDLEARRAY_REF="$1" local COMMAND="$2" local FD_IN FD_OUT # bash 4 has coproc command. if [ ${BASH_VERSINFO[0]} -ge 4 ]; then # We want to pass coproc an array name into which it should write the # I/O handles. If one wants to do this then the command that is # to be executed must be a compount command (i.e. { .... }). If # one passes a compound command then a shell is invoked to execute # the stuff in curly brackets (imagine there are two commands separated # by commas or '&&'; a shell is needed to orchestrate such things). # The result of that is the pid returned in VARNAME_PID is that # of the shell, not of the actual command that you wanted coproc-ed. # To get around this, we have the orchestrating shell *exec* the # desired command, therefore ensuring the PID in VARNAME_PID really # is the one we want. eval "coproc $HANDLEARRAY_REF { exec $COMMAND; } 2>&1" || ade_internal "$ERRSTACK_REF" "ade_start_coproc: coproc failed" # Older bash's need fifos. else ade_debug "$ERRSTACK_REF" 10 "ade_start_coproc: creating fifos ..." ade_register_temp_file "$ERRSTACK_REF" /tmp/$_ADE_PROGNAME.$$.fifo-in || return $? mkfifo /tmp/$_ADE_PROGNAME.$$.fifo-in ade_register_temp_file "$ERRSTACK_REF" /tmp/$_ADE_PROGNAME.$$.fifo-out || return $? mkfifo /tmp/$_ADE_PROGNAME.$$.fifo-out ade_debug "$ERRSTACK_REF" 10 "ade_start_coproc: attaching handles ..." ade_get_free_filehandle "$ERRSTACK_REF" FD_IN || return $? ade_debug "$ERRSTACK_REF" 10 "ade_start_coproc: FD_IN=$FD_IN" eval "exec $FD_IN<>/tmp/$_ADE_PROGNAME.$$.fifo-in" ade_get_free_filehandle "$ERRSTACK_REF" FD_OUT || return $? ade_debug "$ERRSTACK_REF" 10 "ade_start_coproc: FD_OUT=$FD_OUT" eval "exec $FD_OUT<>/tmp/$_ADE_PROGNAME.$$.fifo-out" eval "$HANDLEARRAY_REF[0]=$FD_OUT" eval "$HANDLEARRAY_REF[1]=$FD_IN" ade_debug "$ERRSTACK_REF" 10 "ade_start_coproc: running command in background ..." eval "$COMMAND <&$FD_IN >&$FD_OUT 2>&$FD_OUT" & eval "${HANDLEARRAY_REF}_PID=\$!" fi return $ADE_OK } #DIFFSYNC: evaler ade_evaler() { local ERRSTACK_REF="$1"; shift local COMMAND="$1" if $_ADE_SIMULATE; then echo "$COMMAND" elif ! eval "$COMMAND"; then ade_error "$ERRSTACK_REF" ADE_ERR_MISC "${COMMAND%% }: command failed (hint: see messages above?)" return $ADE_FAIL fi return $ADE_OK } ######################################################################## # # Public functions: temporary file related functions # ######################################################################## #DIFFSYNC: register_exit_function ade_register_exit_function() { local ERRSTACK_REF="$1"; shift _ade_push "$ERRSTACK_REF" _ADE_REGISTERS_CALLONEXIT "$@" # return code will be automatically correctly propogated } #DIFFSYNC: deregister_exit_function ade_deregister_exit_function() { local ERRSTACK_REF="$1"; shift local FUNC _ade_pop_by_value "$ERRSTACK_REF" _ADE_REGISTERS_CALLONEXIT "$@" # return code will be automatically correctly propogated } #DIFFSYNC: register_temp_file ade_register_temp_file() { local ERRSTACK_REF ITEMS I ERRSTACK_REF="$1"; shift ITEMS=("$@") # In line with the perl equivalent functions, we absolutise anything we're # passed to prevent the clean-up-time 'rm -fr' complaining about # 'shell-init: getcwd failed'. # Note that same must be done at deregistration time or we'll not be able # to make a match in the list of registered items. for ((I=0; (($I<${#ITEMS[*]})); ((I++)))); do ade_debug "$ERRSTACK_REF" 200 "ade_register_temp_file: pre-absolution: ${ITEMS[$I]}" ade_get_absolute_path "$ERRSTACK_REF" "${ITEMS[$I]}" "" "ITEMS[$I]" || return $? ade_debug "$ERRSTACK_REF" 200 "ade_register_temp_file: post-absolution: ${ITEMS[$I]}" done _ade_push "$ERRSTACK_REF" _ADE_REGISTERS_DELONEXIT "${ITEMS[@]}" # return code will be automatically correctly propogated } #DIFFSYNC: deregister_temp_file ade_deregister_temp_file() { local ERRSTACK_REF ITEMS I ERRSTACK_REF="$1"; shift ITEMS=("$@") # In line with the perl equivalent functions, we absolutise anything we're # passed to prevent the clean-up-time 'rm -fr' complaining about # 'shell-init: getcwd failed'. # Note that same must be done at deregistration time or we'll not be able # to make a match in the list of registered items. for ((I=0; (($I<${#ITEMS[*]})); ((I++)))); do ade_debug "$ERRSTACK_REF" 200 "ade_deregister_temp_file: pre-absolution: ${ITEMS[$I]}" ade_get_absolute_path "$ERRSTACK_REF" "${ITEMS[$I]}" "" "ITEMS[$I]" || return $? ade_debug "$ERRSTACK_REF" 200 "ade_deregister_temp_file: post-absolution: ${ITEMS[$I]}" done _ade_pop_by_value "$ERRSTACK_REF" _ADE_REGISTERS_DELONEXIT "${ITEMS[@]}" # return code will be automatically correctly propogated } #DIFFSYNC: _deregister_all_exit_functions _ade_deregister_all_exit_functions() { local ERRSTACK_REF="$1"; shift _ADE_REGISTERS_CALLONEXIT=() return $ADE_OK } #DIFFSYNC: _deregister_all_temp_files _ade_deregister_all_temp_files() { local ERRSTACK_REF="$1"; shift local LIST _ADE_REGISTERS_DELONEXIT=() return $ADE_OK } #DIFFSYNC: _delete_all_temp_files _ade_delete_all_temp_files() { local ERRSTACK_REF="$1"; shift rm -fr "${_ADE_REGISTERS_DELONEXIT[@]}" _ade_deregister_all_temp_files "$ERRSTACK_REF" } #DIFFSYNC: _call_all_exit_functions _ade_call_all_exit_functions() { local ERRSTACK_REF="$1"; shift local FUNC for FUNC in "${_ADE_REGISTERS_CALLONEXIT[@]}"; do ade_debug "$ERRSTACK_REF" 10 "_ade_call_all_exit_functions: calling [$FUNC $ERRSTACK_REF] ..." $FUNC "$ERRSTACK_REF" || return $? done _ade_deregister_all_exit_functions "$ERRSTACK_REF" } ######################################################################## # # Public functions: database related functions # ######################################################################## #DIFFSYNC: write_sql ade_write_sql() { local ERRSTACK_REF="$1"; shift local SENDBUF RECVBUF_LOCAL LINE RECVBUF_REF FIRST_LOOP_FLAG ERROR_FLAG ERROR_TYPE ERROR_MSG if [ ${_ADE_DB_SQLITE_PID:-0} = 0 ]; then ade_internal "$ERRSTACK_REF" "ade_write_sql: sqlite is already stopped!" fi # Process arguments SENDBUF="$1" RECVBUF_REF="$2" ade_debug "$ERRSTACK_REF" 10 "ade_write_sql: writing [$SENDBUF] ..." echo "$SENDBUF" >&${COPROC[1]} # Don't try to send and receive the EOL if the command sent is quit; coprocess channels close before the # EOL can be sent and we get an un-'|| true'-able SIGPIPE signal. [ "X$SENDBUF" != X.quit ] || return $ADE_OK echo "select \"EOL\" ;" >&${COPROC[1]} FIRST_LOOP_FLAG=true ERROR_FLAG=false while :; do read -u ${COPROC[0]} LINE || break ade_debug "$ERRSTACK_REF" 10 "ade_write_sql: read [$LINE]" # If line looks like an error of the sort we report, then report it. # It used to be that the text to pass back was directly after the # 'REPORT_TO_USER: ' leader, but with the version of sqlite3 that is # in Debian 12, an SQLite error code is appended to the text, which # was then getting passed up by this code. As a result of that, I've # changed the format of the error text specification (which is in # a trigger in the .sql file) to be 'REPORT_TO_USER: [text-goes-here-in-square-brackets]', # this way this code can detect the end of the user-specified text and # the sqlite3-auto-appended text. if [[ $LINE =~ :\ REPORT_TO_USER:\ \[([^]]*).* ]]; then ade_debug "$ERRSTACK_REF" 10 "ade_write_sql: that's an error to return!" ERROR_FLAG=true ERROR_TYPE=PAA_ERR_MISC ERROR_MSG=${BASH_REMATCH[1]} # If line looks like an error of the sort we don't report, then report it (but without filtering) elif [[ $LINE =~ ^Error:\ (.*) ]]; then ade_debug "$ERRSTACK_REF" 10 "ade_write_sql: that's another kind of error!" ERROR_FLAG=true ERROR_TYPE=ADE_ERR_DB ERROR_MSG=${BASH_REMATCH[1]} elif [ "X$LINE" = XEOL ]; then break elif $FIRST_LOOP_FLAG; then RECVBUF_LOCAL="$LINE" FIRST_LOOP_FLAG=false else RECVBUF_LOCAL="$RECVBUF_LOCAL $LINE" fi done # If error occurred then return it if $ERROR_FLAG; then ade_error "$ERRSTACK_REF" $ERROR_TYPE "$ERROR_MSG" return $ADE_FAIL fi # Silent commands should be silent if [[ $SENDBUF =~ ^(insert|delete|update|INSERT|DELETE|UPDATE) ]] && [ "X$RECVBUF_LOCAL" != X ]; then ade_error "$ERRSTACK_REF" ADE_ERR_DB "$RECVBUF_LOCAL" return $ADE_FAIL fi # If buffer passed then fill it if [ "X$RECVBUF_REF" != X ]; then eval "$RECVBUF_REF=\"\$RECVBUF_LOCAL\"" fi return $ADE_OK } #DIFFSYNC: execute_sql # (not implmented) #DIFFSYNC: select_sql # (not implmented) #DIFFSYNC: select_sql_count # (not implmented) #DIFFSYNC: execute_sql_qm # (not implmented) #DIFFSYNC: select_sql_qm # (not implmented) #DIFFSYNC: select_sql_count_qm # (not implmented) #DIFFSYNC: begin_sql_transaction # (not implemented) #DIFFSYNC: end_sql_transaction # (not implemented) #DIFFSYNC: assert_inside_sql_transaction # (not implemented) #DIFFSYNC: assert_outside_sql_transaction # (not implemented) #DIFFSYNC: show_sql_transaction # (not implemented) #DIFFSYNC: connect_sqlite ade_connect_sqlite() { local ERRSTACK_REF="$1"; shift ade_inherit _ADE_DB_SQLITE_PID || return $? local SEPARATOR EXCLUSIVE_TIMEOUT [ $# = 2 ] || ade_internal "$ERRSTACK_REF" "ade_connect_sqlite: arg count ($#)" SEPARATOR="$1" EXCLUSIVE_TIMEOUT="$2" ade_debug "$ERRSTACK_REF" 10 "_ADE_DB_SQLITE_PID=$_ADE_DB_SQLITE_PID, SEPARATOR=$SEPARATOR, EXCLUSIVE_TIMEOUT=$EXCLUSIVE_TIMEOUT" if [ $_ADE_DB_SQLITE_PID != 0 ]; then ade_internal "$ERRSTACK_REF" "ade_connect_sqlite: sqlite is already running!" else ade_debug "$ERRSTACK_REF" 10 "ade_connect_sqlite: starting sqlite ..." ade_start_coproc "$ERRSTACK_REF" COPROC "$SQLITE_CMD $DB_FILE" || return $? _ADE_DB_SQLITE_PID=$COPROC_PID # Overrule all the things the user might put in their ~/.sqliterc file. ade_write_sql "$ERRSTACK_REF" ".headers off" || return $? ade_write_sql "$ERRSTACK_REF" ".mode list" || return $? ade_write_sql "$ERRSTACK_REF" ".separator '$SEPARATOR'" || return $? ade_write_sql "$ERRSTACK_REF" ".timeout $EXCLUSIVE_TIMEOUT" || return $? ade_write_sql "$ERRSTACK_REF" "pragma foreign_keys = on;" || return $? ade_debug "$ERRSTACK_REF" 10 "ade_connect_sqlite: we believe the PID of sqlite is $_ADE_DB_SQLITE_PID" fi return $ADE_OK } #DIFFSYNC: disconnect_sqlite ade_disconnect_sqlite() { local ERRSTACK_REF="$1"; shift ade_inherit _ADE_DB_SQLITE_PID # inherit from initialisation of ADE's private globals if [ $_ADE_DB_SQLITE_PID = 0 ]; then ade_internal "$ERRSTACK_REF" "ade_disconnect_sqlite: sqlite is already stopped!" else ade_debug "$ERRSTACK_REF" 10 "ade_disconnect_sqlite: stopping sqlite ..." ade_write_sql "$ERRSTACK_REF" ".quit" || return $? wait $_ADE_DB_SQLITE_PID _ADE_DB_SQLITE_PID=0 fi return $ADE_OK } #DIFFSYNC: initialise_sqlite # (not implemented) #DIFFSYNC: validate_sql_schema_version # (not implemented) #DIFFSYNC: get_sql_schema_version # (not implemented) #DIFFSYNC: set_sql_schema_version # (not implemented) #DIFFSYNC: edit_sqlite # (not implemented) #DIFFSYNC: upgrade_database # (not implemented) #DIFFSYNC: filter ade_filter() { # @ARGV must be undefined, else will want to apply the code to the arguments, which it # assumes are files. Undefining @ARGV makes it work on stdin instead. Arguments are # the regexps we want search for. # # The command-line regexps are combined into a single regexp like (?=.*a)(?=.*b)(?=.*c) # which is a series of lookaheads. It's a way to check that a, b and c are all inside # the tested line, without stipulating any ordering requirements. perl -ne 'BEGIN { $re = join("", map { "(?=.*$_)" } @ARGV); undef @ARGV} print if ($_ =~ /$re/);' "$@" } #DIFFSYNC: blank_sql_null ade_blank_sql_null() { local ERRSTACK_REF="$1"; shift local VAR ade_inherit --allow-empty $* || return $? for VAR in $*; do eval "[ \"X\$$VAR\" != XNULL ] || $VAR=" done return $ADE_OK } ######################################################################## # # Public functions: variable related functions # ######################################################################## #DIFFSYNC: inherit ade_inherit() { local VAR EMPTY_ALLOW_FLAG EMPTY_ENSURE_FLAG VAR_EXISTS_FLAG VAR_EMPTY_FLAG # Defaults for options EMPTY_ALLOW_FLAG=false EMPTY_ENSURE_FLAG=false # Process options while [ $# != 0 ]; do case $1 in --reject-empty) EMPTY_ALLOW_FLAG=false EMPTY_ENSURE_FLAG=false ;; --allow-empty) EMPTY_ALLOW_FLAG=true EMPTY_ENSURE_FLAG=false ;; --ensure-empty) EMPTY_ALLOW_FLAG=true EMPTY_ENSURE_FLAG=true ;; --*) shift ;; -*) ade_internal "$ERRSTACK_REF" "inherit: bad option ($1)" ;; *) break ;; esac shift done # Process arguments # Sanity checks and derivations # Guts for VAR in "$@"; do VAR_EXISTS_FLAG=true declare -p $VAR > /dev/null 2>&1 || VAR_EXISTS_FLAG=false VAR_EMPTY_FLAG=true eval "[ \"X\$$VAR\" = X ]" || VAR_EMPTY_FLAG=false ade_debug "$ERRSTACK_REF" 10 "ade_inherit: VAR=$VAR, VAR_EXISTS_FLAG=$VAR_EXISTS_FLAG, VAR_EMPTY_FLAG=$VAR_EMPTY_FLAG" if ! $VAR_EXISTS_FLAG; then ade_internal "$ERRSTACK_REF" "ade_inherit: $VAR: variable not inherited (call stack is ( ${FUNCNAME[*]} )" elif $VAR_EMPTY_FLAG && ! $EMPTY_ALLOW_FLAG; then ade_internal "$ERRSTACK_REF" "ade_inherit: $VAR: variable inherited but empty (call stack is ( ${FUNCNAME[*]} )" elif $EMPTY_ENSURE_FLAG && ! $VAR_EMPTY_FLAG; then ade_internal "$ERRSTACK_REF" "ade_inherit: $VAR: variable inherited but not empty (call stack is ( ${FUNCNAME[*]} )" fi done # No return (this either succeeds or *exits*). } #DIFFSYNC: global ade_global() { : } ######################################################################## # # Public functions: locking related functions # ######################################################################## #DIFFSYNC: lock ade_lock() { local LOCK_FILE PID ERRSTACK_REF [ $# = 2 ] || ade_internal "$ERRSTACK_REF" "ade_lock: bad argument count ($#)" ERRSTACK_REF="$1" LOCK_FILE="$2" ade_debug "$ERRSTACK_REF" 10 "ade_lock: LOCK_FILE=$LOCK_FILE" # Create world-readable temporary lock TMP_LOCK_FILE=$LOCK_FILE.$$ # Discard stderr, in case it does exist and we can't remove it or we can't # create it due to directory permissions. rm -f $TMP_LOCK_FILE 2>/dev/null ade_register_temp_file "$ERRSTACK_REF" $TMP_LOCK_FILE # Order of redirections ensures failure to create temporary lock file not seen. if ! echo 2>/dev/null $$ > $TMP_LOCK_FILE; then # Deregister the temp file as we never created it. ade_deregister_temp_file "$ERRSTACK_REF" $TMP_LOCK_FILE ade_error "$ERRSTACK_REF" ADE_ERR_ACCESS "$TMP_LOCK_FILE" "create" return $ADE_FAIL fi # If can easily lock, return if ln $TMP_LOCK_FILE $LOCK_FILE 2>/dev/null; then # We haven't registered LOCK_FILE before now 'cos weren't certain it was ours. We are now. ade_register_temp_file "$ERRSTACK_REF" $LOCK_FILE rm -f $TMP_LOCK_FILE ade_deregister_temp_file "$ERRSTACK_REF" $TMP_LOCK_FILE return $ADE_OK fi # If lock file not empty and not stale; return PID=$(cat $LOCK_FILE) if [ "X$PID" != X ] && [ -d /proc/$PID ]; then rm -f $TMP_LOCK_FILE ade_deregister_temp_file "$ERRSTACK_REF" $TMP_LOCK_FILE ade_error "$ERRSTACK_REF" ADE_ERR_MISC "lock held by pid $PID" return $ADE_FAIL fi # Remove corrupt or stale lock file ade_warning "$ERRSTACK_REF" ADE_ERR_MISC "$LOCK_FILE: empty or stale; removing ..." rm $LOCK_FILE # Try to lock again (this time we are sure that the lock file will be ours so register now) ade_register_temp_file "$ERRSTACK_REF" $TMP_LOCK_FILE if ln $TMP_LOCK_FILE $LOCK_FILE 2>/dev/null; then rm $TMP_LOCK_FILE ade_deregister_temp_file "$ERRSTACK_REF" $TMP_LOCK_FILE return $ADE_OK fi # It failed in a way we don't understand ade_internal "$ERRSTACK_REF" "ade_lock: locking failed in way not understood" } #DIFFSYNC: unlock ade_unlock() { local LOCK_FILE ERRSTACK_REF [ $# = 2 ] || ade_internal "$ERRSTACK_REF" "ade_unlock: bad argument count ($#)" ERRSTACK_REF="$1" LOCK_FILE="$2" rm $LOCK_FILE ade_deregister_temp_file "$ERRSTACK_REF" $LOCK_FILE return $ADE_OK } ######################################################################## # # Public functions: directory content management functions # ######################################################################## #DIFFSYNC: move_compressed_file # (not implemented) ######################################################################## # # Public functions: miscellaneous # ######################################################################## # (none - and ADE should keep it that way!) ######################################################################## # # Public functions: access module-private variable # ######################################################################## #DIFFSYNC: get_progname ade_get_progname() { local ERRSTACK_REF="$1"; shift local PROGNAME_REF="$1"; shift eval "$PROGNAME_REF=$_ADE_PROGNAME" return $ADE_OK } #DIFFSYNC: get_simulate ade_get_simulate() { local ERRSTACK_REF="$1"; shift local SIMULATE_REF="$1"; shift eval "$SIMULATE_REF=$_ADE_SIMULATE" return $ADE_OK } #DIFFSYNC: get_verboselevel ade_get_verboselevel() { local ERRSTACK_REF="$1"; shift local VERBOSELEVEL_REF="$1"; shift eval "$VERBOSELEVEL_REF=$_ADE_VERBOSELEVEL" return $ADE_OK } #DIFFSYNC: set_progname # (not implemented) #DIFFSYNC: set_simulate # (not implemented) #DIFFSYNC: set_verboselevel # (not implemented) #DIFFSYNC: set_callbacks ade_set_callbacks() { local ERRSTACK_REF="$1"; shift _ADE_USAGE_CALLBACK_REF="$1"; shift _ADE_VERSION_CALLBACK_REF="$1"; shift _ADE_LISTPATHS_CALLBACK_REF="$1"; shift ade_debug "$ERRSTACK_REF" 10 "ade_set_callbacks: registering $_ADE_USAGE_CALLBACK_REF(), $_ADE_VERSION_CALLBACK_REF(), $_ADE_LISTPATHS_CALLBACK_REF() ..." for FUNC_REF in $_ADE_USAGE_CALLBACK_REF $_ADE_VERSION_CALLBACK_REF $_ADE_LISTPATHS_CALLBACK_REF; do [ "X$(type -t $FUNC_REF)" = Xfunction ] || ade_internal "$ERRSTACK_REF" "ade_set_callbacks: $FUNC_REF: not a function" done return $ADE_OK } #DIFFSYNC: set_messaging_parameters ade_set_messaging_parameters() { local ERRSTACK_REF="$1"; shift local KEY VAL KEYVALPAIR WRITER_ID WRITER_IDS NEW_DISPLAY_CALLBACK_REFS # Look at what was passed for KEYVALPAIR in "$@"; do KEY=${KEYVALPAIR%%=*} VAL=${KEYVALPAIR#*=} case $KEY in stack) eval "$VAL=()" ;; dumpall) eval "_ADE_DUMP_WHOLE_ERROR_STACK_FLAG=$VAL" ;; facility) eval "_ADE_WRITER_SYSLOG_FACILITY=$VAL" ;; logfile) eval "_ADE_WRITER_LOGFILE_FILENAME=$VAL" ;; level) eval "_ADE_VERBOSELEVEL=$VAL" ;; writers) NEW_DISPLAY_CALLBACK_REFS=() ade_split_string "$ERRSTACK_REF" "$VAL" "," WRITER_IDS for WRITER_ID in "${WRITER_IDS[@]}"; do if [ "X${_ADE_WRITER_ID_TO_FUNCTION[$WRITER_ID]}" != X ]; then NEW_DISPLAY_CALLBACK_REFS+=( ${_ADE_WRITER_ID_TO_FUNCTION[$WRITER_ID]} ) else ade_internal "$ERRSTACK_REF" "ade_set_messaging_parameters: $WRITER_ID: unknown writer id" fi done _ADE_DISPLAY_CALLBACK_REFS=( "${NEW_DISPLAY_CALLBACK_REFS[@]}" ) ;; *) ade_internal "$ERRSTACK_REF" "ade_set_messaging_parameters: $KEY: unexpected key" ;; esac done return $ADE_OK } ######################################################################## # # Module-private functions # ######################################################################## #DIFFSYNC: _handle_option_V _ade_handle_option_V() { _ade_handle_option_version "$@" } #DIFFSYNC: _handle_option_d _ade_handle_option_d() { _ade_handle_option_debug "$@" } #DIFFSYNC: _handle_option_v _ade_handle_option_v() { _ade_handle_option_verbose "$@" } #DIFFSYNC: _handle_option_h _ade_handle_option_h() { _ade_handle_option_help "$@" } #DIFFSYNC: _handle_option_p _ade_handle_option_p() { _ade_handle_option_paths "$@" } #DIFFSYNC: _handle_option_n _ade_handle_option_n() { _ade_handle_option_simulate "$@" } #DIFFSYNC: _handle_option_version _ade_handle_option_version() { local ERRSTACK_REF="$1"; shift _ADE_SHOW_VERSION_FLAG=true return $ADE_OK } #DIFFSYNC: _handle_option_debug _ade_handle_option_debug() { local ERRSTACK_REF="$1"; shift [[ $1 =~ ^[0-9]+$ ]] || ade_show_bad_usage "$ERRSTACK_REF" ade_set_messaging_parameters "$ERRSTACK_REF" level="$1" return $ADE_OK } #DIFFSYNC: _handle_option_verbose _ade_handle_option_verbose() { local ERRSTACK_REF="$1"; shift ade_set_messaging_parameters "$ERRSTACK_REF" level=3 return $ADE_OK } #DIFFSYNC: _handle_option_help _ade_handle_option_help() { local ERRSTACK_REF="$1"; shift _ADE_SHOW_HELP_FLAG=true return $ADE_OK } #DIFFSYNC: _handle_option_paths _ade_handle_option_paths() { local ERRSTACK_REF="$1"; shift _ADE_SHOW_PATHS_FLAG=true return $ADE_OK } #DIFFSYNC: _handle_option_simulate _ade_handle_option_simulate() { local ERRSTACK_REF="$1"; shift _ADE_SIMULATE=true return $ADE_OK } #DIFFSYNC: _handle_signal _ade_handle_signal() { # Signal handlers need their own stacks local -a ERRSTACK # The equivalent perl function resets the stack here; presumbly because # if the user presses CTRL-C then we simply don't care what is on the # stack any more. ade_info ERRSTACK "clearing up ..." _ade_exit ERRSTACK 4 } #DIFFSYNC: _validate_error_key _ade_validate_error_key() { local ERRSTACK_REF="$1" local DEFINED_ERRORS_HASH_KEY="$2" local ELEMENT_REF="$3" local I J ERRORS_IN_THIS_HASH_REF_COUNT KEY FMT ELEMENT FOUND # Scan over the list of register hashes ... FOUND=false for ((I=0; $I<${#_ADE_REGISTERED_DEFINED_ERRORS_HASH_REFS_ARRAY[*]}; I=$(($I+1)))); do eval "ERRORS_IN_THIS_HASH_REF_COUNT=\${#${_ADE_REGISTERED_DEFINED_ERRORS_HASH_REFS_ARRAY[$I]}[*]}" # In perl we just try to reference the element of the hash which has the # passed key. But in shell, with no hash support, we need to loop over # each element looking for it. for ((J=0; $J<$ERRORS_IN_THIS_HASH_REF_COUNT; J=$(($J+1)))); do # The element consists of 'KEY=keyname; FLD1=val1; FLD2; ...' and this # we can eval. We only need to make sure we define the variables as # local in order to prevent the assignment being seen in a wider scope. eval "ELEMENT=\${${_ADE_REGISTERED_DEFINED_ERRORS_HASH_REFS_ARRAY[$I]}[\$J]}" eval "$ELEMENT" if [ "X$KEY" = "X$DEFINED_ERRORS_HASH_KEY" ]; then FOUND=true break fi done ! $FOUND || break done if ! $FOUND; then ade_internal "$ERRSTACK_REF" "$DEFINED_ERRORS_HASH_KEY: unknown error" fi # This allows this function not only to be used to validate error keys, but also # to return the corresponding field hash. if [ "X$ELEMENT_REF" != X ]; then # If *this* shell is allowed to expand $ELEMENT then the eval will go wrong because element # includes some quotes. The trick is then to delay evaluation until the thing has safely # been quoted and then let the second level evaluation expand it. eval "$ELEMENT_REF=\"\$ELEMENT\"" fi return $ADE_OK } #DIFFSYNC: _call_registered_message_writers _ade_call_registered_message_writers() { local ERRSTACK_REF TEMPLATE MESSAGE LEVEL SYSLOG_LEVEL local WRITERFUNC_REF ERRSTACK_REF="$1" TEMPLATE="$2" MESSAGE="$3" LEVEL="$4" SYSLOG_LEVEL="$5" TEMPLATE="${TEMPLATE//\%MESSAGE/$MESSAGE}" TEMPLATE="${TEMPLATE//\%LEVEL/$LEVEL}" for WRITERFUNC_REF in "${_ADE_DISPLAY_CALLBACK_REFS[@]}"; do $WRITERFUNC_REF "$ERRSTACK_REF" "$TEMPLATE" $LEVEL $SYSLOG_LEVEL done return $ADE_OK } #DIFFSYNC: _push _ade_push() { local ERRSTACK_REF="$1"; shift local LIST="$1"; shift for THING in "$@"; do ade_debug "$ERRSTACK_REF" 200 "_ade_push: adding $THING to $LIST ..." # Note how evaluation of $THING is delayed until after eval has evaluated the line (this is in case # $THING contains double-quoates, which would screw up quoting. eval "$LIST=( \"\${$LIST[@]}\" \"\$THING\" )" done return $ADE_OK } #DIFFSYNC: _pop _ade_pop() { local ERRSTACK_REF="$1"; shift local LIST_REF="$1" # Quotes needed as '[' and ']' trigger globbing. Note that unset really only # deletes an element in the expected way *for the last* element (it does not # not reindex later elements!). unset "$LIST_REF[-1]" } #DIFFSYNC: _pop_by_value _ade_pop_by_value() { local ERRSTACK_REF="$1"; shift local LIST_REF="$1"; shift local DEL_VAL TMP_ARRAY VAL for DEL_VAL in "$@"; do TMP_ARRAY=() eval "for VAL in \"\${$LIST_REF[@]}\"; do [[ \$VAL = \$DEL_VAL ]] || TMP_ARRAY+=(\"\$VAL\"); done" eval "$LIST_REF=(\"\${TMP_ARRAY[@]}\")" done return $ADE_OK } #DIFFSYNC: _pop_all # (not implemented) #DIFFSYNC: _join _ade_join() { # Copied from https://dev.to/meleu/how-to-join-array-elements-in-a-bash-script-303a local ERRSTACK_REF="$1"; shift local SEPARATOR="$1" local REF=$2 shift 2 local FIRST="$1" shift printf -v $REF "%s" "$FIRST" "${@/#/$SEPARATOR}" } #DIFFSYNC: _internal _ade_internal() { local ERRSTACK_REF MESSAGE ERRSTACK_REF="$1" MESSAGE="$2" _ade_call_registered_message_writers "$ERRSTACK_REF" "INTERNAL ERROR: %MESSAGE" "$MESSAGE" 0 crit || return $? return $ADE_OK } #DIFFSYNC: _error _ade_error() { local ERRSTACK_REF MESSAGE ERRSTACK_REF="$1" MESSAGE="$2" _ade_call_registered_message_writers "$ERRSTACK_REF" "ERROR: %MESSAGE" "$MESSAGE" 1 err || return $? return $ADE_OK } #DIFFSYNC: _warning _ade_warning() { local ERRSTACK_REF MESSAGE ERRSTACK_REF="$1" MESSAGE="$2" _ade_call_registered_message_writers "$ERRSTACK_REF" "WARNING: %MESSAGE" "$MESSAGE" 2 warning || return $? return $ADE_OK } #DIFFSYNC: _info _ade_info() { local ERRSTACK_REF MESSAGE ERRSTACK_REF="$1" MESSAGE="$2" _ade_call_registered_message_writers "$ERRSTACK_REF" "INFO: %MESSAGE" "$MESSAGE" 3 info || return $? return $ADE_OK } #DIFFSYNC: _debug _ade_debug() { local ERRSTACK_REF LEVEL MESSAGE ERRSTACK_REF="$1" LEVEL="$2" MESSAGE="$3" _ade_call_registered_message_writers "$ERRSTACK_REF" "DEBUG[%LEVEL]: %MESSAGE" "$MESSAGE" $LEVEL debug || return $? return $ADE_OK } #DIFFSYNC: _display_error_stack_stderr _ade_display_error_stack_stderr() { local ERRSTACK_REF TEXT LEVEL SYSLOG_LEVEL ERRSTACK_REF="$1" TEXT="$2" LEVEL=$3 SYSLOG_LEVEL=$4 if (($LEVEL > $_ADE_VERBOSELEVEL)); then return $ADE_OK fi echo "$_ADE_PROGNAME: $TEXT" >&2 return $ADE_OK } #DIFFSYNC: _display_error_stack_log_file _ade_display_error_stack_log_file() { local ERRSTACK_REF TEXT LEVEL SYSLOG_LEVEL ERRSTACK_REF="$1" TEXT="$2" LEVEL=$3 SYSLOG_LEVEL=$4 # Sanity checks and derivations # *this* function must not call a message-displaying function else recurses! [ "X$_ADE_WRITER_LOGFILE_FILENAME" != X ] || return $ADE_OK # Guts echo "$(date '+%Y/%m/%dT%H:%M:%S'): $TEXT" >> $_ADE_WRITER_LOGFILE_FILENAME return $ADE_OK } #DIFFSYNC: _display_error_stack_syslog _ade_display_error_stack_syslog() { local ERRSTACK_REF TEXT LEVEL SYSLOG_LEVEL ERRSTACK_REF="$1" TEXT="$2" LEVEL=$3 SYSLOG_LEVEL=$4 if (($LEVEL > $_ADE_VERBOSELEVEL)); then return $ADE_OK fi logger -i -t $_ADE_PROGNAME -p $_ADE_WRITER_SYSLOG_FACILITY.$SYSLOG_LEVEL "$TEXT" return $ADE_OK } #DIFFSYNC: _display_error_stack_dev_null _ade_display_error_stack_dev_null() { local ERRSTACK_REF TEXT LEVEL SYSLOG_LEVEL ERRSTACK_REF="$1" TEXT="$2" LEVEL=$3 SYSLOG_LEVEL=$4 return $ADE_OK } #DIFFSYNC: _initialise _ade_initialise() { local _ADESH_TEMPORARY_ERRSTACK # CDPATH causes all sorts of troubles! CDPATH= # Register the error types that ADE itself will handle. (Note that this function # does not take an error stack, because what error code would it use if it wanted # to report an error, when the error codes have not already been registered!?) ade_register_error_types _ADE_DEFINED_ERRORS # Set up auto-clean-up. trap _ade_handle_signal 1 2 15 # bash does not default to providing '*(pat1|pat2)' pathname expansion. # ksh does. This is needed for efficient sed'ing of strings by the shell, # rather than with sed. Here we enable this feature. # 20240608: commented out by Alexis on the assumption this is not referenced any more. #[ "X$BASH_VERSION" = X ] || shopt -s extglob # The python and perl versions of this library now initialise _ADE_DISPLAY_CALLBACK_REFS[] to # a list of function references. But in bash function references are strings so the bash # version of this library can do initialise it along with other (private) globals at the # top of this file. # Function replacements. #_ade_replace_function _ADESH_TEMPORARY_ERRSTACK old_name new_name _ade_replace_function _ADESH_TEMPORARY_ERRSTACK ade_reset_error_stack ade_set_messaging_parameters _ade_replace_function _ADESH_TEMPORARY_ERRSTACK ade_eval ade_evaler _ade_replace_function _ADESH_TEMPORARY_ERRSTACK ade_lock_simple ade_lock _ade_replace_function _ADESH_TEMPORARY_ERRSTACK ade_unlock_simple ade_unlock } #DIFFSYNC: _validate_function # (not implemented) #DIFFSYNC: _replace_function _ade_replace_function() { local ERRSTACK_REF ERRSTACK local OLD_FNCREF NEW_FNCREF # Note that we can't use $ERRSTACK_REF in the ade_internal() call # until we are sure there are three arguments, so we - just for that # one call - need a local stack. Really all calls to ade_internal() # that report bad argument counts should use a temporary error stack. [ $# = 3 ] || ade_internal ERRSTACK "_ade_replace_function: bad argument count ($#)" ERRSTACK_REF="$1" OLD_FNCREF=$2 NEW_FNCREF=$3 eval "$OLD_FNCREF() { local ERRSTACK_REF=\"\$1\" local CALL_STACK if [ \"X\$ADE_REPLACE_FUNCTION_SILENT\" != X ]; then : elif [ \"X\$_ADE_REPLACE_FUNCTION_ALERTED_$OLD_FNCREF\" != X ]; then : else _ade_join \"ERRSTACK_REF\" \", \" CALL_STACK \"\${FUNCNAME[@]}\" ade_warning \"\$ERRSTACK_REF\" ADE_ERR_MISC \"$OLD_FNCREF() has been superceded by $NEW_FNCREF(), call stack is: \$CALL_STACK\" _ADE_REPLACE_FUNCTION_ALERTED_$OLD_FNCREF=1 fi $NEW_FNCREF \"\$@\" }" } #DIFFSYNC: _fork_multi_debug # (not implemented) #DIFFSYNC: _exit # This contains the *only* call to exit. _ade_exit() { local ERRSTACK_REF="$1"; shift local RC=$1; shift _ade_delete_all_temp_files "$ERRSTACK_REF" _ade_call_all_exit_functions "$ERRSTACK_REF" # This line maps ADE function return codes into Unix program exit codes. # It should be the only call to 'exit' anywhere. exit $RC } ######################################################################## # # Public variables # ######################################################################## ADE_OK=0 # only these should be public! ADE_FAIL=1 ######################################################################## # # Module-private variables with public access functions # ######################################################################## _ADE_PROGNAME=${0##*/} _ADE_SIMULATE=false _ADE_VERBOSELEVEL=2 _ADE_LISTPATHS_CALLBACK_REF= _ADE_USAGE_CALLBACK_REF= _ADE_VERSION_CALLBACK_REF= _ADE_DISPLAY_CALLBACK_REFS=( _ade_display_error_stack_stderr ) ######################################################################## # # Other module-private variables # ######################################################################## _ADE_DEFINED_ERRORS=( "KEY=ADE_ERR_UNDEFINED; FMT=\"%s: undefined %s\"" "KEY=ADE_ERR_UNEXPECTED; FMT=\"%s: expected %s, got %s\"" "KEY=ADE_ERR_ACCESS; FMT=\"%s: can't %s\"" "KEY=ADE_ERR_SEEABOVE; FMT=\"error detected; see higher frames for more information\"" "KEY=ADE_ERR_MISC; FMT=\"%s\"" "KEY=ADE_ERR_EOF; FMT=\"%s: end of file\"" "KEY=ADE_ERR_NOTIMPLEMENTED; FMT=\"%s: not implemented\"" "KEY=ADE_ERR_DB; FMT=\"database error: %s\"" ) declare -A _ADE_WRITER_ID_TO_FUNCTION=( [stderr]=_ade_display_error_stack_stderr [devnull]=_ade_display_error_stack_dev_null [syslog]=_ade_display_error_stack_syslog [logfile]=_ade_display_error_stack_log_file ) _ADE_DUMP_WHOLE_ERROR_STACK_FLAG=false _ADE_WRITER_LOGFILE_FILENAME= _ADE_WRITER_SYSLOG_FACILITY=local0 _ADE_SHOW_PATHS_FLAG=false _ADE_SHOW_HELP_FLAG=false _ADE_SHOW_VERSION_FLAG=false #_ADE_INSIDE_SQL_TRANSACTION_FLAG=false _ADE_REGISTERS_CALLONEXIT=() _ADE_REGISTERS_DELONEXIT=() _ADE_REGISTERED_DEFINED_ERRORS_HASH_REFS_ARRAY=() _ADE_REGISTERED_OPTS=() _ADE_DB_SQLITE_PID=0 ######################################################################## # # Actual code # ######################################################################## _ade_initialise