##  $HeadURL$ $LastChangedRevision$

##  Packages required by this package
import subprocess
import re
import os
import sys
import getopt
import signal
import types

##  Declare exports
#BEGIN {
#    use Exporter   ();
#    our (@ISA, @EXPORT);
#    @ISA         = qw(Exporter);
#    @EXPORT      = qw(ade_gep_main $ade_tmp_dir $ade_app_progname $ade_err_ok $ade_err_fail ade_fnm_basename ade_err_warning ade_err_info ade_err_debug ade_tmp_registerfile ade_tmp_deregisterfile ade_err_internal ade_err_error ade_msg_usage $ade_app_progname ade_fnm_basename ade_fnm_makeabsolute stupid_error_in_ade_space ade_err_registerdefderrs %ade_defined_errors ade_fcm_openwritecompressed ade_fcm_openreadcompressed ade_msg_version ade_smf_extractversionfromsvnstring ade_msg_listpaths ade_spc_autogendheader ade_spc_rollingstatusmanager ade_smf_isvalidregexp ade_mua_addmailheader ade_mua_sendmail ade_err_resetstack ade_usr_amiroot ade_fnm_dirname ade_spc_procopts ade_std_true ade_std_false ade_err_instack ade_msg_askquestion ade_smf_validate_float ade_smf_validate_integer);
#}
#
#END { 
#}       
#
##  Instantiate exported data
#our($ade_err_ok, $ade_err_fail);
#our($ade_tmp_dir);
#our($ade_app_progname);
#
##  Initialise exported data
#($ade_err_ok, $ade_err_fail) = (0, 1);
[ ade_err_ok, ade_err_fail ] = [ 0, 1 ]
#($ade_app_progname) = ($0 =~ /.*\/([^\/]*)$/);
ade_app_progname = sys.argv[0].rpartition("/")[2]
#
##  Instantiate private data
#my($sendmail_cmd, %ade_defined_errors);
ade_defined_errors = {}
#my(@ade_registered_defined_errors_hash_refs_array);
ade_registered_defined_errors = {}
#my($ade_err_dumpwholestack, $ade_msg_writer_logfile_filename, @ade_err_writerfunc_refs);
#my($ade_app_usage_text_getter_ref, $ade_app_version_text_getter_ref, $ade_app_listpaths_text_getter_ref);
ade_err_writerfunc_refs = []
#my(%ade_registers);
ade_registers = { 'delonexit':{}, 'callonexit':{} }

#my($ade_msg_verboselevel);
ade_msg_verboselevel = 0     #  this is mean to be instantiation only
#
##  Initialise private data 
##  This could be moved inside ade-config.sh
#$sendmail_cmd = "/usr/lib/sendmail";
##  Outside ADE, nobody should *use* ADE error codes, but maybe some
##  people will test for them (e.g. to reset the errorstack in the event
##  of a handlable error). But on the other handle the *tag* will be 
##  made public, not the array.
ade_defined_errors = {
    "ade_err_undefined":      { "fmt": "%s: undefined %s" },
    "ade_err_access":         { "fmt": "%s: can't %s" },
    "ade_err_locked":         { "fmt": "%s: locked by %s" },
    "ade_err_convert":        { "fmt": "%s: couldn't convert from %s to %s" },
    "ade_err_seeabove":       { "fmt": "error detected; see higher frames for more information" },
    "ade_err_invalid":        { "fmt": "%s: invalid %s" },
    'ade_err_misc':           { "fmt": "%s" },
    "ade_err_eof":            { "fmt": "%s: end of file" },
    "ade_err_notimplemented": { "fmt": "%s: not implemented" }
}
##  ADE can deal with the follow errors (initial none)
ade_registered_defined_errors = {}
#$ade_err_dumpwholestack = 1;
#$ade_msg_writer_logfile_filename = undef;
# ade_err_writerfunc_refs initialisation delayed to avoid forward reference (see below)
#ade_err_writerfunc_refs = [ _ade_msg_writer_stderr ]
#%ade_registers = ();
#$ade_msg_verboselevel = 2;
ade_msg_verboselevel = 0
ade_registered_opts = []
ade_registered_opt = {}
(ade_app_usage_text_getter, ade_app_version_text_getter, ade_app_listpaths_text_getter) = (None, None, None)

###############################################################################
##
##  PUBLIC FUNCTIONS/ENTRY FUNCTIONS
##
###############################################################################

def ade_gep_main(app_main_ref):
    errstack = []
    #ade_err_resetstack(errstack, stack=errstack, dumpall=1)
    ade_err_resetstack(errstack, stack=errstack, dumpall=1, level=100)
    rc = _ade_gep_initialise_with_stack(errstack)
    if (rc == ade_err_ok):
        rc = _ade_gep_main_withstack(errstack, app_main_ref)
    if (rc != ade_err_ok):
        ade_err_displaystack(errstack)
        ade_err_resetstack(errstack, stack=errstack)
    _ade_gep_exit(errstack, rc)

#
###############################################################################
##
##  PUBLIC FUNCTIONS/ERROR STACK FUNCTIONS
##
###############################################################################

def ade_err_resetstack(errstack_ref, stack=None, dumpall=None, logfile=None, level=None, writers=None):
    global ade_err_dumpwholestack, ade_msg_writer_logfile_filename, ade_msg_verboselevel, ade_err_writerfunc_refs

    if stack is not None:
        #  See http://stackoverflow.com/questions/1400608/how-to-empty-a-list-in-python.
        del stack[:]
    if dumpall is not None:
        ade_err_dumpwholestack = dumpall
    if logfile is not None:
        ade_msg_writer_logfile_filename = logfile
    if level is not None:
        ade_msg_verboselevel = level
    if writers is not None:
        ade_err_writerfunc_refs.append(writers)

    return ade_err_ok
    
#
##
##  Is a specified error in the stack? 
##
##  (This is used to say things like "Did that function fail
##  because there are no mails available? If so then don't
##  treat that as an error, and I will handle the situation
##  myself.")
##
#
#sub ade_err_instack
#{
#    my($errstack_ref, $err, $instack_flagref) = @_;
#    my($i, $framecount);
#   
#    #  Facilitate doing it the C way with '<' in the 'for' loop instead of '<='.
#    $framecount = $#{$errstack_ref}+1;
#    #  Assume not found to proved otherwise.
#    ${$instack_flagref} = undef;
#    #  Scan the stack for the error
#    for ($i=0; $i<$framecount; $i++) {
#        if (${$errstack_ref}[$i]{err} eq $err) {
#            ${$instack_flagref} = $i;
#        }
#    }
#
#    return($ade_err_ok);
#}

def ade_err_registerdefderrs(defined_errors):
    global ade_registered_defined_errors
    ade_registered_defined_errors.update(defined_errors)
    return ade_err_ok

# ade_err_displaystack definition delayed to avoid forward reference (see below)

def ade_err_internal(errstack, message):
    #  The error frames on the stack may be relevant to solving the
    #  problem, so dump them.
    ade_err_displaystack(errstack)
    _ade_err_displayinternal(errstack, message)
    sys.exit(5)

def ade_err_error(errstack, defined_errors_hash_key, *errpars):
    rc = _ade_err_validate_errorkey(errstack, defined_errors_hash_key)
    if rc != ade_err_ok:
        return rc
    errstack.append({ "err":defined_errors_hash_key, "par":errpars })
    return ade_err_ok

def ade_err_warning(errstack, defined_errors_hash_key, *errpars):
    rc = _ade_err_validate_errorkey(errstack, defined_errors_hash_key)
    if rc != ade_err_ok:
        return rc
    errstack.append({ "err":defined_errors_hash_key, "par":errpars })
    ade_err_displaystack(errstack, _ade_err_displaywarning)
    ade_err_resetstack(errstack, stack=errstack)
    return ade_err_ok

def ade_err_info(errstack, message):

    return _ade_err_displayinfo(errstack, message)

def ade_err_debug(errstack, level, message):

    return _ade_err_displaydebug(errstack, level, message)

##############################################################################
#
#  PUBLIC FUNCTIONS/MESSAGING FUNCTIONS
#
##############################################################################

def ade_msg_register(errstack, usage_text_getter, version_text_getter, listpaths_text_getter):
    global ade_app_usage_text_getter, ade_app_version_text_getter, ade_app_listpaths_text_getter

    ade_app_usage_text_getter = usage_text_getter
    ade_app_version_text_getter = version_text_getter
    ade_app_listpaths_text_getter = listpaths_text_getter

    return ade_err_ok

def ade_msg_usage(errstack, exit_code=1):
    global ade_app_usage_text_getter

    ade_err_debug(errstack, 10, "ade_msg_usage: getting pass 1 text ...")
    (rc, usage_pass1_text) = ade_app_usage_text_getter(errstack, 1)
    if rc != ade_err_ok:
        return rc
    ade_err_debug(errstack, 10, "ade_msg_usage: pass 1 text is [%s]" % (usage_pass1_text))

    ade_err_debug(errstack, 10, "ade_msg_usage: getting pass 2 text ...")
    (rc, usage_pass2_text) = ade_app_usage_text_getter(errstack, 2)
    if rc != ade_err_ok:
        return rc
    ade_err_debug(errstack, 10, "ade_msg_usage: pass 2 text is [%s]" % (usage_pass2_text))

    if (exit_code):
        sys.stderr.write("%s: ERROR: type '%s --help' for correct usage.\n" % (ade_app_progname, ade_app_progname))
    else:
        sys.stdout.write("Usage:   %s [ <options> ] " % (ade_app_progname))
	if usage_pass1_text is not None:
            sys.stdout.write(usage_pass1_text)
        sys.stdout.write("\n")
        sys.stdout.write("\n") 
        sys.stdout.write("Options: -V        | --version               display program version\n")
        sys.stdout.write("         -v        | --verbose               verbose\n")
        sys.stdout.write("         -d        | --debug=<level>         set debug level\n")
        sys.stdout.write("         -h        | --help                  display this text\n")
        sys.stdout.write("         -p        | --list-paths            list used paths\n")
        if usage_pass2_text is not None:
	    sys.stdout.write(usage_pass2_text)
        sys.stdout.write("\n")

    _ade_gep_exit(errstack, exit_code)

def ade_msg_version(errstack):

    (rc, version_text) = ade_app_version_text_getter(errstack)
    if rc != ade_err_ok:
        return rc
    if version_text is not None:
	sys.stdout.write("%s version %s\n" % (ade_app_progname, version_text))
    _ade_gep_exit(errstack, 0)

def ade_msg_listpaths(errstack):

    (rc, listpaths_text) = ade_app_listpaths_text_getter(errstack)
    if rc != ade_err_ok:
        return rc
    if listpaths_text is not None:
	sys.stdout.write(listpaths_text + "\n")

    _ade_gep_exit(errstack, 0)

#sub ade_msg_getverboselevel
#{
#    my ($errstack_ref, $verboselevel_ref) = @_;
#
#    ${$verboselevel_ref} = $ade_msg_verboselevel;
#
#    return($ade_err_ok);
#}
#
#sub ade_msg_askquestion
#{
#    my($errstack_ref, $hint, $prompt, $default, $validate_fnc, $rationalise_fnc, $rationalised_response_ref) = @_;
#    my($response, $rc, $validated);
#
#    if (defined($hint)) {
#        &ade_err_debug($errstack_ref, 20, "ade_msg_askquestion: hint is defined; displaying ...");
#        print "$hint\n";
#    }
#
#    while (1) {
#        print "$ade_app_progname: QUESTION: $prompt [$default]: ";
#        #  If this an error occurs below (e.g. 'cos user hits CTRL-D and this function
#        #  returns ade_err_eof) then the error output will arrive *before* the text 
#        #  printed above. This is due to it not having a new line and the output being
#        #  buffered. To avoid this confusion we flush immediately. This uses IO::Handle.
#        STDOUT->flush();
#        $response = <STDIN>;
#        if (!defined($response)) {
#            &ade_err_error($errstack_ref, ade_err_eof, "stdin");
#            return($ade_err_fail);
#        }
#        chomp($response);
#        ($response eq '')  && ($response = $default);
#        ($response eq '.') && ($response = '');
#        &ade_err_debug($errstack_ref, 20, "ade_msg_askquestion: \$response=[$response]");
#        if (($rc=&$validate_fnc($errstack_ref, $response, \$validated)) != $ade_err_ok) {
#            return($rc);
#        } elsif ($validated) {
#            last;
#        }
#        &ade_err_warning($errstack_ref, ade_err_misc, "invalid response; please retry");
#    }
#
#    if (!defined($rationalise_fnc)) {
#        ${$rationalised_response_ref} = $response;
#    } elsif (($rc=&$rationalise_fnc($errstack_ref, $response, $rationalised_response_ref)) != $ade_err_ok) {
#        return($rc);
#    } 
#
#    return($ade_err_ok);
#}

###############################################################################
##
##  PUBLIC FUNCTIONS/MATHS FUNCTIONS
##  
###############################################################################
#
#sub ade_mth_minimum
#{
#    my($errstack_ref, $a, $b, $returnval_ref) = @_;
#  
#    ${$returnval_ref} = (($a<$b)?$a:$b);
#
#    return($ade_err_ok);
#}
#
################################################################################
##
##  PUBLIC FUNCTIONS/TEMPORARY FILE MANAGEMENT
##
################################################################################
#
#sub ade_tmp_registerfunc
#{
#    my($errstack_ref, @items) = @_;
#
#    return(&_ade_tmp_register($errstack_ref, "callonexit", @items));
#}
#
#sub ade_tmp_deregisterfunc
#{
#    my($errstack_ref, @items) = @_;
#
#    return(&_ade_tmp_deregister($errstack_ref, "callonexit", @items));
#}

def ade_tmp_registerfile(errstack, item):
    (rc, abs_item) = ade_fnm_makeabsolute(errstack, item, None)
    if rc != ade_err_ok:
        return rc
    return _ade_tmp_register(errstack, 'delonexit', abs_item)

def ade_tmp_deregisterfile(errstack, item):
    (rc, abs_item) = ade_fnm_makeabsolute(errstack, item, None)
    if rc != ade_err_ok:
        return rc
    return _ade_tmp_deregister(errstack, 'delonexit', abs_item)

################################################################################
##
##  PUBLIC FUNCTIONS/STANDARDIZED COMMANDS
##
################################################################################
#
#sub ade_std_true
#{
#    my ($errstack_ref) = @_;
#
#    return($ade_err_ok);
#}
#
#sub ade_std_false
#{
#    my ($errstack_ref) = @_;
#
#    &ade_err_error($errstack_ref, ade_err_misc, "false-forced error");
#
#    return($ade_err_fail);
#}
#
#sub ade_std_which
#{
#    my ($errstack_ref, $program, $return_value_ref) = @_;
#    my ($dir, $found);
#
#    if ($program =~ /^\// && -f $program && -x $program) {
#        ${$return_value_ref} = $program;
#    } elsif ($program =~ /^\//) {
#        &ade_err_error($errstack_ref, ade_err_access, $program, "find");
#        return($ade_err_fail);
#    } else {
#        $found = 0;
#        foreach $dir (split(/:/, $ENV{'PATH'})) {
#            if (-f "$dir/$program" && -x "$dir/$program") {
#                ${$return_value_ref} = "$dir/$program";
#                $found = 1;
#                last;
#            }
#        }
#        if (!$found) {
#            &ade_err_error($errstack_ref, ade_err_access, $program, "find");
#            return($ade_err_fail);
#        }
#    }
#
#    return($ade_err_ok);
#}
#
################################################################################
##
##  PUBLIC FUNCTIONS/STRING MANIPULATION FUNCTIONS
##
################################################################################

def ade_smf_extractversionfromsvnstring(errstack, svnstring):

    ade_err_debug(errstack, 50, "ade_smf_extractversionfromsvnstring: sof (svnstring=%s)" % svnstring)
    if [ m for m in [ re.search("\$HeadURL: .*?/(trunk)/.*?\$ \$LastChangedRevision: (\d+) \$", svnstring) ] if m is not None ]:
        return(ade_err_ok, "svn/trunk/" + m.group(2))
    elif [ m for m in [ re.search("\$HeadURL: .*?/tags/([^/]+)/.*?\$ \$LastChangedRevision: \d+ \$", svnstring) ] if m is not None ]:
        return(ade_err_ok, m.group(1))
    elif [ m for m in [ re.search("\$HeadURL: .*?/branches/([^/]+)/.*?\$ \$LastChangedRevision: \d+ \$", svnstring) ] if m is not None ]:
        return(ade_err_ok, "svn/branch/" + m.group(1))
    else:
        ade_err_internal(errstack, "ade_smf_extractversionfromsvnstring: handling of \"%s\" not implemented yet" % (svnstring))

#sub ade_smf_isvalidregexp
#{
#    my($errstack_ref, $regex) = @_;
#    my($coderef);
#
#    if ($coderef = eval { "junk" =~ /$regex/ }) { }
#
#    if (!defined($coderef)) {
#        &ade_err_error($errstack_ref, ade_err_invalid, $regex, "regexp");
#        return($ade_err_fail);
#    }
#
#    return($ade_err_ok);
#}
#
#sub ade_smf_validate_float
#{
#    my($errstack_ref, $value, $validated_ref) = @_;
#    
#    #  ABC.DEF (where A is 1-9)
#    #  0.DEF
#    #  .DEF
#    #  ABC (where A is 1-9)
#    #  0
#
#    ${$validated_ref} = ($value =~ /^[1-9][0-9]*\.[0-9]+$/ || 
#                         $value =~ /^0?\.[0-9]+$/ ||
#                         $value =~ /^[1-9][0-9]*$/ ||
#                         $value =~ /^0$/);
#
#    return($ade_err_ok);
#}
#
#
#sub ade_smf_validate_integer
#{
#    my($errstack_ref, $value, $validated_ref) = @_;
#    
#    #  ABC (where A is 1-9)
#    #  0
#
#    ${$validated_ref} = ($value =~ /^[1-9][0-9]*$/ ||
#                         $value =~ /^0$/);
#
#    return($ade_err_ok);
#}
#
################################################################################
##
##  PUBLIC FUNCTIONS/FILENAME MANIPULATION
##
################################################################################
#
#sub ade_fnm_dirname
#{
#    my($errstack_ref, $file, $dir_ref) = @_;
#
#    if (!defined($file)) {
#        #  We use error rather than internal so as to be able to report more information in the caller.
#        &ade_err_error($errstack_ref, "ade_fnm_dirname: \$file: undefined");
#        return($ade_err_fail);
#    
#    } elsif (!defined($dir_ref)) {
#        #  We use error rather than internal so as to be able to report more information in the caller.
#        &ade_err_error($errstack_ref, "ade_fnm_dirname: \$dir_ref: undefined");
#        return($ade_err_fail);
#    }
#
#    #  Strip trailing slashes (if not made entirely of slashes). I'm sure POSIX has something to
#    #  say on how multiple slashes should be interpreted but this will be okay for now.
#    $file =~ s/\/+$// if ($file !~ /^\/+$/);
#
#    #  aaa --> .
#    if ($file =~ /^[^\/]+$/) {
#        ${$dir_ref} = ".";
#
#    #  /aaa --> /
#    } elsif ($file =~ /^\/[^\/]+$/) {
#        ${$dir_ref} = "/";
#
#    #  aaa/bbb/ccc --> aaa/bbb
#    #  /aaa/bbb/ccc --> /aaa/bbb
#    #  aaa/bbb/ccc/ --> aaa/bbb
#    #  /aaa/bbb/ccc/ --> /aaa/bbb
#    } else {
#        (${$dir_ref}) = ($file =~ /^(.*)\/[^\/]+$/);
#    }
#
#    return($ade_err_ok);
#}
#
#sub ade_fnm_basename 
#{
#    my($errstack_ref, $file, $basename_ref) = @_;
#
#    $file =~ /^.*\/([^\/]+)/;
#    ${$basename_ref} = $1;
#
#    return($ade_err_ok);
#}

##  Name:     ade_fnm_makeabsolute - convert paths to absolute paths
##  Synopsis: ade_fnm_makeabsolute(<error-stack-ref>, <path>, { <absdir> | undef }, <abspathref>)
def ade_fnm_makeabsolute(errstack, what, cwd):
    if cwd is None:
        cwd = os.getcwd()
    what = os.path.normpath(what)
    if what[0] != '/':
        what = cwd + '/' + what
    return (ade_err_ok, what)

###############################################################################
##
##  PUBLIC FUNCTIONS/DIRECTORY CONTENT MANIPULATION
##
###############################################################################
#
#sub ade_dcm_movecompressedfile
#{
#    my($errstack_ref, $srcname, $dstname) = @_;
#    my($rc);
#
#    &ade_err_debug($errstack_ref, 50, "ade_dcm_movecompressedfile: sof (srcname=$srcname, dstname=$dstname");
#
#    if (($srcname =~ /\.gz$/ && $dstname =~ /\.gz$/) || 
#            ($srcname =~ /\.Z$/ && $dstname =~ /\.Z$/) ||
#            ($srcname !~ /\.(?:Z|gz)$/ && $dstname  !~ /\.(?:Z|gz)$/)) {
#        &ade_err_debug($errstack_ref, 50, "ade_dcm_movecompressedfile: using 'mv' ...");
#        #  Linux 'mv' can go interactive, so stdin redirected to avoid this.
#        if (($rc=system("mv $srcname $dstname < /dev/null 2>/dev/null")) != 0) {
#            &ade_err_debug($errstack_ref, 50, sprintf("ade_dcm_movecompressedfile: exit: %d, sig: %d, dump: %d", ($rc>>8), ($rc&127), ($rc&128)));
#            &ade_err_error($errstack_ref, ade_err_access, $srcname, "mv");
#            return($ade_err_fail);
#        }
#        &ade_err_debug($errstack_ref, 50, "ade_dcm_movecompressedfile: using 'mv' done");
#    } else {
#        &ade_err_debug($errstack_ref, 50, "ade_dcm_movecompressedfile: using ade_fcm_openreadcompressed|ade_fcm_openwritecompressed ...");
#        if (($rc=&ade_fcm_openreadcompressed($errstack_ref, $srcname, \*SRC_HANDLE)) != $ade_err_ok) {
#            &ade_err_error($errstack_ref, ade_err_access, $srcname, "open");
#            return($rc);
#        }
#        if (($rc=&ade_fcm_openwritecompressed($errstack_ref, $dstname, \*DST_HANDLE)) != $ade_err_ok) {
#            &ade_err_error($errstack_ref, ade_err_access, $dstname, "open");
#            return($rc);
#        }
#        while (<SRC_HANDLE>) {
#            print DST_HANDLE;
#        }
#        close(SRC_HANDLE);
#        close(DST_HANDLE);
#        if (!unlink($srcname)) {
#            &ade_err_error($errstack_ref, ade_err_access, $srcname, "unlink");
#            return($ade_err_fail);
#        }
#    }
#
#    return($ade_err_ok);
#}
#
################################################################################
##
##  PUBLIC FUNCTIONS/USER-RELATED
##
################################################################################
#
#sub ade_usr_getmyloginname
#{
#    my ($errstack_ref, $id_ref) = @_;
#
#    ${$id_ref} = (getpwuid($<))[0];
#
#    return($ade_err_ok);
#}
#
#sub ade_usr_amiroot
#{
#    my ($errstack_ref, $i_am_root_ref) = @_;
#
#    ${$i_am_root_ref} = ($> == 0) ? 1 : 0;
#
#    return($ade_err_ok);
#}
#
################################################################################
##
##  FILE CONTENT MANIPULATION
##
################################################################################
#
#sub ade_fcm_openreadcompressed
#{
#    my($errstack_ref, $filename, $handle) = @_;
#    my($rc, $gunzip_cmd, $uncompress_cmd);
#
#    return($ade_err_fail) if (! -r $filename);
#
#    if (! -r $filename) {
#        &ade_err_error($errstack_ref, ade_err_access, $filename, "read");
#        return($ade_err_fail);
#    } elsif ($filename =~ /^.*\.gz$/) {
#        if (($rc=&ade_std_which($errstack_ref, 'gunzip', \$gunzip_cmd)) != $ade_err_ok) {
#            return($rc);
#        } elsif (!open($handle, "$gunzip_cmd < $filename |")) {
#            &ade_err_error($errstack_ref, ade_err_access, $filename, "open with '$gunzip_cmd'");
#            return($ade_err_fail);
#        }
#    } elsif ($filename =~ /^.*\.Z$/) {
#        if (($rc=&ade_std_which($errstack_ref, 'uncompress', \$uncompress_cmd)) != $ade_err_ok) {
#            return($rc);
#        } elsif (!open($handle, "$uncompress_cmd < $filename |")) {
#            &ade_err_error($errstack_ref, ade_err_access, $filename, "open with '$uncompress_cmd'");
#            return($ade_err_fail);
#        }
#    } else {
#        if (!open($handle, $filename)) {
#            &ade_err_error($errstack_ref, ade_err_access, $filename, "open");
#            return($ade_err_fail);
#        }
#    }
#
#    return($ade_err_ok);
#}
#
#sub ade_fcm_openwritecompressed
#{
#    my($errstack_ref, $filename, $handle) = @_;
#    my($rc, $gzip_cmd, $compress_cmd);
#
#    if ($filename =~ /^.*\.gz$/) {
#        if (($rc=&ade_std_which($errstack_ref, 'gzip', \$gzip_cmd)) != $ade_err_ok) {
#            return($rc);
#        } elsif (!open($handle, "| $gzip_cmd > $filename")) {
#            &ade_err_error($errstack_ref, ade_err_access, $filename, "open with '$gzip_cmd'");
#            return($ade_err_fail);
#        }
#    } elsif ($filename =~ /^.*\.Z$/) {
#        if (($rc=&ade_std_which($errstack_ref, 'compress', \$compress_cmd)) != $ade_err_ok) {
#            return($rc);
#        } elsif (!open($handle, "| $compress_cmd > $filename")) {
#            &ade_err_error($errstack_ref, ade_err_access, $filename, "open with '$compress_cmd'");
#            return($ade_err_fail);
#        }
#    } else {
#        if (!open($handle, ">$filename")) {
#            &ade_err_error($errstack_ref, ade_err_access, $filename, "open");
#            return($ade_err_fail);
#        }
#    }
#
#    return($ade_err_ok);
#}

###############################################################################
#
#  PUBLIC FUNCTIONS/LOCKING
#
###############################################################################

def ade_lck_lock(errstack, lock_what, gen_lock_file_name_fnc=None):
    ade_err_debug(errstack, 10, 'lock: locking ...')

    if lock_what.startswith('/'):
        lock_file = lock_what
    else:
        lock_file = gen_lock_file_name_fnc(errstack, lock_what)
    tmp_lock_file = '%s.%d' % (lock_file, os.getpid())

    #  create temporary lock (world readable)
    old_umask = os.umask(0022)
    ade_tmp_registerfile(errstack, tmp_lock_file)
    fp = open(tmp_lock_file, 'w')
    os.umask(old_umask)
    fp.write('%d\n' % os.getpid())
    fp.close()

    #  slide the temporary lockfile into place
    ade_tmp_registerfile(errstack, lock_file)
    try:
        os.link(tmp_lock_file, lock_file)
        os.unlink(tmp_lock_file)
        ade_tmp_deregisterfile(errstack, tmp_lock_file)
        return ade_err_ok
    except:
        ade_tmp_deregisterfile(errstack, lock_file)

    #  Inspect existing lock file contents
    fp = open(lock_file)
    holding_pidstr = fp.readline()
    fp.close()
    rc = _ade_lck_validate_lockfile_contents(errstack, holding_pidstr)
    if rc != ade_err_ok:
        os.unlink(tmp_lock_file)
        ade_tmp_deregisterfile(errstack, tmp_lock_file)
        ade_err_error(errstack, 'ade_err_misc', '%s: corrupt lock file' % (lock_file))
        return ade_err_fail
    holding_pid = int(holding_pidstr.rstrip())

    #  Check if the PID is running or is stale
    (rc,running_flag) = ade_lck_checklock(errstack, holding_pid)
    if rc != ade_err_ok:
        return rc
    if running_flag:
        os.unlink(tmp_lock_file)
        ade_err_error(errstack, 'ade_err_misc', '%s is already running (pid is %d)' % (ade_app_progname, holding_pid))
        return ade_err_fail

    #  Lock is stale; remove it 
    os.unlink(lock_file)

    #  try locking again
    try:
        ade_tmp_registerfile(errstack, lock_file)
        os.link(tmp_lock_file, lock_file)
        os.unlink(tmp_lock_file)
        ade_tmp_deregisterfile(errstack, tmp_lock_file)
        return ade_err_ok
    except:
        pass

    #  locking failed ... again; give up
    os.unlink(tmp_lock_file)
    ade_tmp_deregisterfile(errstack, tmp_lock_file)
    ade_err_internal(errstack, 'lck_lock: couldn\'t lock even after removing stale lockfile')

#MISSING: ade_lck_getgenericlockfilename
#sub ade_lck_getgenericlockfilename
#{
#    return("$ade_tmp_dir/$ade_app_progname.pid");
#}

def ade_lck_checklock(errstack, pid):
    proc_dir_for_pid = '/proc/%d' % (pid)
    running_flag = os.path.isdir('/proc/%d' % (pid))
    return(ade_err_ok, running_flag)

def ade_lck_unlock(errstack, lock_what, gen_lock_file_name_fnc=None):
    ade_err_debug(errstack, 10, 'unlock: unlocking ...')
    if lock_what.startswith('/'):
        lock_file = lock_what
    else:
        lock_file = gen_lock_file_name_fnc(errstack, lock_what)
    os.unlink(lock_file)

    return ade_err_ok

##############################################################################
#
#  PUBLIC FUNCTIONS/OPTION-PROCESSING
#
##############################################################################

def funcname_to_funcref(errstack, globals_dict, funcname, empty_array):
    ade_err_debug(errstack, 10, "funcname_to_funcref: converting %s to a function ref or None ..." % (funcname))
    if funcname not in globals_dict:
        empty_array[:] = [ None ]
    if type(globals_dict[funcname]) is not types.FunctionType:
        empty_array[:] = [ None ]
    else:
        empty_array[:] = [ globals_dict[funcname] ]
    return ade_err_ok

def ade_opt_register(errstack, short_opts, long_opts, globals_dict, opt_func_template="option_handler_%s"):
    global ade_register_opts
    empty_array = []

    ade_err_debug(errstack, 10, "ade_opt_register: short_opts=%s, long_opts=%s, opt_func_template=%s" % tuple([ (x,"None")[x is None] for x in [short_opts, long_opts, opt_func_template ] ]))
    for opt_type in ("short", "long"):
        if opt_type == "short":
	    opts = short_opts
	    regexp = '^([a-zA-Z])(:[siof])?(.*)$'
        else:
            opts = long_opts
            regexp = '^([a-zA-Z][-a-zA-Z0-9]+)(:[siof])?,?(.*)$'
	if opts is None:
	    continue
        ade_err_debug(errstack, 10, "ade_opt_register: opt_type=%s, opts=%s, re=%s" % (opt_type, opts, regexp))
        while [ m for m in [ re.search(regexp, opts) ] if m is not None ]:
	    opt = m.group(1)
            ade_err_debug(errstack, 10, "ade_opt_register: opt=%s" % (opt))
	    if m.group(2) is None:
	    	opt_argc = 0
		opt_argt = None
	    else:
	    	opt_argc = 1
		opt_argt = m.group(2)[1]
	    opts = m.group(3)
            opt_suff = opt
	    opt_suff = opt_suff.replace("-","_")
	    opt_func = opt_func_template
	    opt_func = opt_func.replace("%s", opt_suff)
            ade_err_debug(errstack, 10, "ade_opt_register: opt=%s, opt_suff=%s, opt_argc=%s, opt_argt=%s, opt_func=%s, opts=%s" % tuple([ (x,"None")[x is None] for x in [ opt, opt_suff, opt_argc, opt_argt, opt_func, opts ] ]))
            rc = funcname_to_funcref(errstack, globals_dict, opt_func, empty_array)
            if rc != ade_err_ok:
                return rc
            subref = empty_array[0]
	    if subref is None:
                ade_err_debug(errstack, 10, "ade_opt_register: %s: undefined function" % (opt_func))
                ade_err_error(errstack, 'ade_err_undefined', [ opt_func, "function"])
                return(ade_err_fail)
            ade_registered_opt[opt] = [ opt_type, subref, opt_argc, opt_argt ]
            ade_registered_opts.append(opt)

    return ade_err_ok

def ade_opt_process(errstack):
    global ade_registered_opts, ade_opt_want_to_show_paths, ade_opt_want_to_show_usage, ade_opt_want_to_show_version
    opt_hash = {}

    ade_err_debug(errstack, 10, "ade_opt_process: assembling hash ...")
    for opt in ade_registered_opts:
        ade_err_debug(errstack, 10, "ade_opt_process: assembling hash entry for %s ..." % (opt))
        opt_suff = opt
        opt_suff = opt_suff.replace("-", "_")
        ade_err_debug(errstack, 10, "ade_opt_process: dumping ade_registered_opt[%s]: %s" % (opt_suff, ade_registered_opt[opt]))
        opt_type = ade_registered_opt[opt][0]
        opt_func = ade_registered_opt[opt][1]
        opt_argc = ade_registered_opt[opt][2]
        opt_argt = ade_registered_opt[opt][3]
        ade_err_debug(errstack, 10, "ade_opt_process: opt=%s, opt_suff=%s, opt_argc=%s, opt_argt=%s, opt_func=%s" % tuple([(x,"None")[x is None] for x in [opt, opt_suff, opt_argc, opt_argt, opt_func]]))
    getopts_short_opts = "".join([ "%s%s" % (x, ("",":")[ade_registered_opt[x][2]]) for x in ade_registered_opt.keys() if ade_registered_opt[x][0] == "short" ])
    getopts_long_opts = [ "%s%s" % (x, ("","=")[ade_registered_opt[x][2]]) for x in ade_registered_opt.keys() if ade_registered_opt[x][0] == "long" ]

    try:
        optlist, sys.argv = getopt.getopt(sys.argv[1:], getopts_short_opts, getopts_long_opts)
    except getopt.GetoptError:
        ade_msg_usage(errstack)
    ade_err_debug(errstack, 10, "ade_opt_process: optlist = %s" % (optlist))
    ade_err_debug(errstack, 10, "ade_opt_process: sys.argv = %s" % (sys.argv))
    for opt_tuple in optlist:
        if [ m for m in [ re.search("^--(.*)", opt_tuple[0]) ] if m is not None ]:
	    opt_suff = m.group(1)
        elif [ m for m in [ re.search("^-(.*)", opt_tuple[0]) ] if m is not None ]:
	    opt_suff = m.group(1)
        else:
            ade_err_internal(errstack, "ade_opt_process: opt_tuple[0] did not start with '--' or '-'")
        #  Call the option handler that was registered for this option.
        ade_err_debug(errstack, 10, "ade_opt_process: handler is %s" % (ade_registered_opt[opt_suff][1]))
        if ade_registered_opt[opt_suff][2] == 0:
            ade_registered_opt[opt_suff][1](errstack)
        else:
            ade_registered_opt[opt_suff][1](errstack, opt_tuple[1])
        ade_err_debug(errstack, 10, "ade_opt_process: ade_opt_want_to_show_paths=%d, ade_opt_want_to_show_usage=%d, ade_opt_want_to_show_version=%d" % (ade_opt_want_to_show_paths, ade_opt_want_to_show_usage, ade_opt_want_to_show_version))

    if (ade_opt_want_to_show_paths):
        rc = ade_msg_listpaths(errstack)
        ade_err_internal(errstack, "ade_opt_process: ade_msg_listpaths() unexpectedly returned (rc=%d)" % (rc))
    if (ade_opt_want_to_show_usage):
        rc = ade_msg_usage(errstack, 0)
        ade_err_internal(errstack, "ade_opt_process: ade_msg_usage() unexpectedly returned (rc=%d)" % (rc))
    if (ade_opt_want_to_show_version):
        rc = ade_msg_version(errstack)
        ade_err_internal(errstack, "ade_opt_process: ade_msg_version() unexpectedly returned (rc=%d)" % (rc))

    return ade_err_ok

###############################################################################
##
##  PUBLIC FUNCTIONS/SPECIAL
##
###############################################################################
#
##  Rolling Status Manager
#sub ade_spc_rollingstatusmanager
#{
#    my ($errstack_ref, $mode, $sched_file, $scan_funcref, $comp_funcref, $midhook_funcref,
#            $oldscan_stem, $oldscan_compext, $log_file, $scanarg, $tmp_dir, $rollcount) = @_;
#    my($rc, $newest_status_file, $i, $newer_status_file, $older_status_file);
#
#    &ade_err_debug($errstack_ref, 10, "ade_spc_rollingstatusmanager: mode=$mode, sched_file=$sched_file, oldscan_stem=$oldscan_stem, oldscan_compext=$oldscan_compext, log_file=$log_file, scanarg=$scanarg, tmp_dir=$tmp_dir, rollcount=$rollcount");
#
#    $newest_status_file = "$tmp_dir/$ade_app_progname.$$.snap";
#
#    #  For init, check or refresh, do a new scan.
#    if ($mode eq "init" || $mode eq "check" || $mode eq "refresh") {
#        &ade_tmp_registerfile($errstack_ref, $newest_status_file);
#        #  Get ready to write to the (compressed) new scan file.
#        &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: opening new scan file $newest_status_file ...");
#        if (($rc=&ade_fcm_openwritecompressed($errstack_ref, $newest_status_file, \*NEWSCAN_HANDLE)) != $ade_err_ok) {
#            &ade_err_error($errstack_ref, ade_err_access, $newest_status_file, "open");
#            return($rc);
#        }
#        #  In a moment we'll redirect stdout, but before we do we need to
#        #  remember where stdout is pointing now so that we can restore it
#        #  in a moment.
#        &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: remembering stdout ...");
#        #  Avoid names that might clash with user applications for the handle, since it is a global.
#        if (!open(ADESPCRSM_OLD_STDOUT, ">&STDOUT")) {
#            &ade_err_internal($errstack_ref, "ade_spc_rollingstatusmanager: can't dup stdout");
#        }
#	#  Here we redirect it.
#        &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: redirecting stdout ...");
#        open(STDOUT, ">&NEWSCAN_HANDLE");
#        #  Now scan_funcref() can just merrily output to stdout.
#        &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: calling the scanning callback ...");
#        if (($rc=&$scan_funcref($errstack_ref, $scanarg)) != $ade_err_ok) {
#            &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: scanning callback failed");
#            close NEWSCAN_HANDLE;
#            open(STDOUT, ">&ADESPCRSM_OLD_STDOUT") || &ade_err_internal($errstack_ref, "ade_spc_rollingstatusmanager: can't dup stdout");
#        }
#        &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: scanning callback successful");
#        #  Restore stdout.
#        &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: restoring stdout ...");
#        if (!open(STDOUT, ">&ADESPCRSM_OLD_STDOUT")) {
#            &ade_err_internal($errstack_ref, "ade_spc_rollingstatusmanager: can't restore stdout");
#        }
#        #  Close the (compressed) new scan file.
#        &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: closing scan handle ...");
#        close NEWSCAN_HANDLE;
#    }
#
#    #  For check or refresh, compare the old and new scans.
#    if ($mode eq "check" || $mode eq "refresh") {
#        for ($i=$rollcount; $i>0; $i--) {
#            if ($i == 1) {
#                $newer_status_file = $newest_status_file;
#                $older_status_file = sprintf("%s%s", $oldscan_stem, $oldscan_compext);
#            } elsif ($i == 2) {
#                $newer_status_file = sprintf("%s%s", $oldscan_stem, $oldscan_compext);
#                $older_status_file = sprintf("%s.%s%s", $oldscan_stem, $i, $oldscan_compext);
#            } else {
#                $newer_status_file = sprintf("%s.%s%s", $oldscan_stem, $i-1, $oldscan_compext);
#                $older_status_file = sprintf("%s.%s%s", $oldscan_stem, $i, $oldscan_compext);
#            }
#            &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: \$rollcount=$rollcount, \$i=$i, \$older_status_file=$older_status_file, \$newer_status_file=$newer_status_file");
#
#            #  When first running this, there may not be enough 'old'
#            #  scan logs to make all the desired comparisons. i.e.
#	    #  oldest with second-to-oldest, second-to-oldest with
#	    #  third-from-oldest, etc. This isn't really an error; it's
#	    #  just a natural part of the initial use of this rolling
#	    #  state monitor. If/when it does happen, then clearly we
#	    #  need to move on to the 'slightly newer' - which may 
#            #  exist - for comparison.
#            #
#            #  Actually, there is an exception to this; and that is
#	    #  when we only maintain *one* file older than the temporary
#	    #  copy. And in that case this older file should *always*
#            #  exist; if it doesn't then we've obviously gone wrong
#            #  in reaching this point in the program.
#            if (! -f $older_status_file) {
#                if ($rollcount > 1) {
#                    next;
#                } else {
#                    &ade_err_error($errstack_ref, ade_err_access, $older_status_file, "access");
#                    return($ade_err_fail);
#                }
#            }
#
#            &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: doing a comparision ...");
#            if (defined($log_file)) {
#                if (($rc=&ade_fcm_openwritecompressed($errstack_ref, $log_file, \*LOG_HANDLE)) != $ade_err_ok) {
#                    &ade_err_error($errstack_ref, ade_err_access, $log_file, "open");
#                    return($rc);
#                } else {
#                    &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: \$log_file not defined; not an error");
#                }
#                #  Avoid names that might clash with user applications for the handle, since it is a global.
#                if (!open(ADESPCRSM_OLD_STDOUT, ">&STDOUT")) {
#                    &ade_err_internal($errstack_ref, "ade_spc_rollingstatusmanager: can't dup stdout");
#                }
#                open(STDOUT, ">&LOG_HANDLE");
#            }
#            if (($rc=&$comp_funcref($errstack_ref, $older_status_file, $newer_status_file, $i)) != $ade_err_ok) {
#                #  don't bother checking if open produces an error when we are
#		#  already in an error situation.
#                if (defined($log_file)) {
#                    open(STDOUT, ">&ADESPCRSM_OLD_STDOUT");
#                    close LOG_HANDLE;
#                    #  We have to inform the user of where they can get the error
#	            #  because zdiff (called from anonymous callback in wkrep)
#	            #  is not well behaved about where it sends errors to. Try
#	            #  'zdiff asdfg sdfdfg > /dev/null' and there is no error
#	            #  message on stdout saying 'files do not exist'. But to
#	            #  assume that the error is *never* displayed and cat the
#	            #  log file *here* would be too presumtive. 
#                    &ade_err_error($errstack_ref, ade_err_misc, "comparison failed; $log_file may provide more information");
#                } else {
#                    &ade_err_error($errstack_ref, ade_err_misc, "comparison failed");
#                }
#                return($rc);
#            }
#            &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: comparing callback successful");
#            if (defined($log_file)) {
#                if (!open(STDOUT, ">&ADESPCRSM_OLD_STDOUT")) {
#                    &ade_err_internal($errstack_ref, "ade_spc_rollingstatusmanager: can't restore STDOUT: $!");
#                }
#                close LOG_HANDLE;
#            }
#        }
#    }
#
#    #  This hook is specifically for checkntp to allow it to write
#    #  'marking NTP ok even though it failed and is about to be restarted
#    #  so that in five minutes when checkntp runs again, even though NTP
#    #  will not be ok and all historical records show it never was ok we
#    #  still do not restart it.'
#    if (defined($midhook_funcref)) {
#        if (($rc=&$midhook_funcref($errstack_ref, $newest_status_file)) != $ade_err_ok) {
#            &ade_err_error($errstack_ref, ade_err_misc, "midhook function failed");
#            return($rc);
#        }
#    }
#    
#    #  For check with schedule, or refresh, or init, make the new snap the 
#    #  old snap. Don't do it by moving or copying 'cos we may move a gzipped 
#    #  file to a non-gzipped name, which will cause problems.
#    if (($mode eq "check" && defined($sched_file) && -f $sched_file) || $mode eq "refresh" || $mode eq "init") {
#        for ($i=$rollcount; $i>0; $i--) {
#            if ($i == 1) {
#                $newer_status_file = $newest_status_file;
#                $older_status_file = sprintf("%s%s", $oldscan_stem, $oldscan_compext);
#            } elsif ($i == 2) {
#                $newer_status_file = sprintf("%s%s", $oldscan_stem, $oldscan_compext);
#                $older_status_file = sprintf("%s.%s%s", $oldscan_stem, $i, $oldscan_compext);
#            } else {
#                $newer_status_file = sprintf("%s.%s%s", $oldscan_stem, $i-1, $oldscan_compext);
#                $older_status_file = sprintf("%s.%s%s", $oldscan_stem, $i, $oldscan_compext);
#            }
#
#            #  This is the same as the comparsion case when there isn't
#	    #  the needed 'old' scan files.
#            if (! -f $newer_status_file) {
#                if ($rollcount > 1) {
#                    next;
#                } else {
#                    &ade_err_error($errstack_ref, ade_err_access, $newer_status_file, "access for stealing");
#                    return($ade_err_fail);
#                }
#            }
#
#            &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: shuffling up ($newer_status_file -> $older_status_file) ...");
#            if (($rc=&ade_dcm_movecompressedfile($errstack_ref, $newer_status_file, $older_status_file)) != $ade_err_ok) {
#                &ade_err_error($errstack_ref, ade_err_access, $newer_status_file, "move");
#                return($rc);
#            }
#        }
#
#        #  The temporary snapshot file has been preserved under a proper name, so 
#        #  we no longer need to delete it.
#        &ade_tmp_deregisterfile($errstack_ref, $newest_status_file);
#    }
#
#    #  For check with schedule, or refresh, delete the schedule file.
#    if (defined($sched_file) && (($mode eq "check" && -f $sched_file) || $mode eq "refresh")) {
#        &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: removing sched file ...");
#        unlink($sched_file);
#    }
#
#    #  For scheduling, create the schedule file.
#    if ($mode eq "schedule") {
#        if (!defined($sched_file)) {
#            &ade_err_internal($errstack_ref, "ade_spc_rollingstatusmanager: called in 'schedule' mode, but the schedule file is undefined");
#        }
#        &ade_err_debug($errstack_ref, 40, "ade_spc_rollingstatusmanager: creating sched file ...");
#        if (!open(HANDLE, ">$sched_file")) {
#            &ade_err_error($errstack_ref, ade_err_access, $sched_file, "create");
#            return($ade_err_fail);
#        }
#        close HANDLE;
#    }
#
#    #  For checks without a schedule file we can now delete the temporary snapshot.
#    unlink($newest_status_file);
#    &ade_tmp_deregisterfile($errstack_ref, $newest_status_file);
#
#    return($ade_err_ok);
#}
#
#sub ade_spc_autogendheader
#{
#    my($errstack_ref, $outhandle, $comment_start, $comment_end, $headersize_ref) = @_;
#    my($user, $unamen, $date, $linelength, $textlength, $rc);
#
#    &ade_err_debug($errstack_ref, 4, "ade_spc_autogendheader: sof (COS=$comment_start, COE=$comment_end)");
#
#    $comment_start = '#' if (!defined($comment_start));
#    $comment_end = '' if (!defined($comment_end));
#
#    if (($rc=&ade_usr_getmyloginname($errstack_ref, \$user)) != $ade_err_ok) {
#        return($rc);
#    }
#    chop($unamen = `uname -n`);
#    $linelength = 78;
#    chop($date = `date`);
#    $textlength = $linelength - (length($comment_start) + length($comment_end));
#    printf $outhandle "%s%-*s%s\n", $comment_start, $textlength, "=" x $textlength, $comment_end;
#    printf $outhandle "%s%-*s%s\n", $comment_start, $textlength, "", $comment_end;
#    printf $outhandle "%s%-*s%s\n", $comment_start, $textlength, "               T H I S   I S   A   G E N E R A T E D   F I L E", $comment_end;
#    printf $outhandle "%s%-*s%s\n", $comment_start, $textlength, "", $comment_end;
#    printf $outhandle "%s%-*s%s\n", $comment_start, $textlength, "               M A N U A L   E D I T S   M A Y   B E   L O S T", $comment_end;
#    printf $outhandle "%s%-*s%s\n", $comment_start, $textlength, "", $comment_end;
#    printf $outhandle "%s%-*s%s\n", $comment_start, $textlength, "  Generated by:      $user", $comment_end;
#    printf $outhandle "%s%-*s%s\n", $comment_start, $textlength, "  Generated with:    $ade_app_progname", $comment_end;
#    printf $outhandle "%s%-*s%s\n", $comment_start, $textlength, "  Generated on host: $unamen", $comment_end;
#    printf $outhandle "%s%-*s%s\n", $comment_start, $textlength, "  Generated on date: $date", $comment_end;
#    printf $outhandle "%s%-*s%s\n", $comment_start, $textlength, "", $comment_end;
#    printf $outhandle "%s%-*s%s\n", $comment_start, $textlength, "=" x $textlength, $comment_end;
#    
#    #  pass the number of lines add to the the parent.
#    ${$headersize_ref} = 12 if (defined($headersize_ref));
#
#    return($ade_err_ok);
#}
#
#sub ade_spc_cachemanager
#{
#    my ($errstack_ref, $cache_file, $cache_expiry_period, $desired_cache_expiry, $new_cache_tester_fncref, $new_cache_getter_fncref, $cache_id) = @_;
#    my ($actual_cache_expiry, $cache_dir, $refresh_cache, $rc);
#
#    #  Determine if cache actually expired
#    $actual_cache_expiry = (time - (stat($cache_file))[9] >= 86400 * $cache_expiry_period);
#
#    #  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) {
#        $refresh_cache = 1;
#    } elsif (!$actual_cache_expiry) {
#        $refresh_cache = 0;
#    } elsif (!defined($desired_cache_expiry)) {
#        $refresh_cache = 1;
#    } elsif (-f $cache_file) {
#        $refresh_cache = 0;
#    } else {
#        &ade_err_warning($errstack_ref, "cache will be refreshed against user's desire because no cache file exists yet");
#        $refresh_cache = 1;
#    }
#
#    &ade_err_debug($errstack_ref, 4, "ade_spc_cachemanager: desired_cache_expiry=$desired_cache_expiry, actual_cache_expiry=$actual_cache_expiry, refresh_cache=$refresh_cache");
#        
#    if ($refresh_cache) {
#        return($rc) if (($rc=&ade_fnm_dirname($errstack_ref, $cache_file, \$cache_dir)) != $ade_err_ok);
#
#        # sanity check cache
#        if (-f $cache_file) {
#            if (! -w $cache_file) {
#                &ade_err_error($errstack_ref, ade_err_misc, "$cache_file: cache file exists but is not writable!") if (! -w $cache_file);
#                return($ade_err_fail);
#            }
#        } elsif (-d $cache_dir) {
#            if (! -w $cache_dir) {
#                &ade_err_error($errstack_ref, ade_err_misc, "$cache_file: cannot create cache file") if (! -w $cache_dir);
#                return($ade_err_fail);
#            }
#        } else {
#            &ade_err_error($errstack_ref, ade_err_misc, "$cache_dir: cache file's directory does not exist");
#            return($ade_err_fail);
#        }
#             
#        #  update cache
#        &ade_err_info($errstack_ref, "updating '$cache_id' cache, please wait ... ");
#        &ade_tmp_registerfile($errstack_ref, "$ade_tmp_dir/$ade_app_progname.$$.newcache");
#        &ade_err_debug($errstack_ref, 4, "ade_spc_cachemanager: calling $new_cache_getter_fncref() ...");
#        if (!open(TMPCACHE_HANDLE, ">$ade_tmp_dir/$ade_app_progname.$$.newcache")) {
#            &ade_err_error($errstack_ref, ade_err_access, "$ade_tmp_dir/$ade_app_progname.$$.newcache", "open");
#            return($ade_err_fail);
#        }
#        #  Avoid names that might clash with user applications for the handle, since it is a global.
#        if (!open(ADESPCCM_OLD_STDOUT, ">&STDOUT")) {
#            &ade_err_internal($errstack_ref, "ade_spc_cachemanager: can't dup stdout");
#        }
#        open(STDOUT, ">&TMPCACHE_HANDLE");
#        &$new_cache_getter_fncref;
#        if (!open(STDOUT, ">&ADESPCCM_OLD_STDOUT")) {
#            &ade_err_internal($errstack_ref, "ade_spc_cachemanager: can't dup stdout");
#        }
#        close(TMPCACHE_HANDLE);
#
#        #  if tests pass, accept it.
#        if (&$new_cache_tester_fncref("$ade_tmp_dir/$ade_app_progname.$$.newcache") == 0) {
#            system("cat $ade_tmp_dir/$ade_app_progname.$$.newcache > $cache_file");
#            unlink("$ade_tmp_dir/$ade_app_progname.$$.newcache");
#            &ade_tmp_deregisterfile($errstack_ref, "$ade_tmp_dir/$ade_app_progname.$$.newcache");
#            &ade_err_info($errstack_ref, "'$cache_id' cache update succeeded");
#
#        #  if tests fail but we have an old version, reject with warning
#        } elsif (-f $cache_file) {
#            &ade_tmp_deregisterfile($errstack_ref, "$ade_tmp_dir/$ade_app_progname.$$.newcache");
#            &ade_err_warning($errstack_ref, "'$cache_id' cache update failed, old cache available (see $ade_tmp_dir/$ade_app_progname.$$.newcache for new data which failed acceptance test)");
#
#        #  if tests fail and we don't have an old version, reject with error
#        } else {
#            &ade_tmp_deregisterfile($errstack_ref, "$ade_tmp_dir/$ade_app_progname.$$.newcache");
#            &ade_err_error($errstack_ref, ade_err_misc, "'$cache_id' cache update failed, old cache not available (see $ade_tmp_dir/$ade_app_progname.$$.newcache for new data which failed acceptance test)");
#            return($ade_err_fail);
#        }
#    }
#
#    #  Pass cache to parent
#    system("cat $cache_file");
#
#    #  success return code
#    return($ade_err_ok);
#}
#
###############################################################################
##
##  PUBLIC FUNCTIONS/MAIL
##
###############################################################################
#
#sub ade_mua_addmailheader
#{
#    my($errstack_ref, $headers_ref, $header_line) = @_;
#    my($header_name, $header_value, $current_element_count);
#
#    ($header_name, $header_value) = split(/:\s+/, $header_line, 2);
#    $current_element_count = scalar keys(%$headers_ref);
#    &ade_err_debug($errstack_ref, 4, "ade_mua_addmailheader: number of elements so far is $current_element_count");
#    $$headers_ref{$header_name} = {
#        lineno => $current_element_count,
#        value  => $header_line
#    };
#
#    return($ade_err_ok);
#}
#
#sub ade_mua_delmailheader
#{
#    my($errstack_ref, $headers_ref, $header_name) = @_;
#
#    delete($$headers_ref{$header_name});
#
#    return($ade_err_ok);
#}
#
##sub ade_mua_loadrcfile
##{
##    my($headers_ref) = @_;
##    my($mymailrc_file, $rc);
##
##    $mymailrc_file = $ENV{'HOME'} . "/.mymailrc";
##    #  It is not an error for this file to be inaccessible.
##    if (open(MYMAILRC_HANDLE, $mymailrc_file)) {
##        while (<MYMAILRC_HANDLE>) {
##            chomp;
##            &ade_err_debug($errstack_ref, 4, "ade_mua_loadrcfile: read line $_");
##            if (/^\s*$/) {
##                next;
##            } elsif (/^\s*#.*$/) {
##                next;
##            } else {
##                if (($rc=&ade_mua_addmailheader($headers_ref, $_)) != 0) {
##                    &ade_err_error($errstack_ref, ade_err_misc, "ade_mua_addmailheader() failed, returning early ...");
##                    return($rc);
##                }
##            }
##        }
##        close(MYMAILRC_HANDLE);
##    }
##
##    return($ade_err_ok);
##}
#
#sub ade_mua_sendmail
#{
#    my($errstack_ref, $headers_ref, $body_handle, $mailto_ref) = @_;
#    my($user, $smtp, $mymailrc_file, $key, $rc);
#
#    # 
#    #  Catch relative and unexecutable sendmail. (Previously these were done at start-time, not
#    #  at when-sendmail-actually-needed-time, but this presents problems to users - e.g. Thomas -
#    #  who don't have sendmail and don't want it and won't write programs that use it.
#    #
#
#    if ($sendmail_cmd && $sendmail_cmd !~ /^\//) {
#        &ade_err_error($errstack_ref, ade_err_misc, "sendmail command ($sendmail_cmd) is not absolute");
#        return($ade_err_fail);
#
#    } elsif ($sendmail_cmd && ! -x $sendmail_cmd) {
#        &ade_err_error($errstack_ref, ade_err_misc, "can't execute $sendmail_cmd");
#        return($ade_err_fail);
#
#    #
#    #  If sendmail command defined then use it.
#    #
#
#    } elsif ($sendmail_cmd) {
#        &ade_err_debug($errstack_ref, 4, "ade_mua_sendmail: mailing with $sendmail_cmd");
#        if (!open(PIPE_HANDLE, "| $sendmail_cmd " . join(' ', @$mailto_ref))) {
#            &ade_err_error($errstack_ref, ade_err_access, "open pipe to $sendmail_cmd");
#            return($ade_err_fail);
#        }
#        print PIPE_HANDLE map { $$headers_ref{$_}{value} . "\n" } (sort { $$headers_ref{$a}{lineno} <=> $$headers_ref{$b}{lineno} } keys %$headers_ref);
#        print PIPE_HANDLE "\n";
#        while (<$body_handle>) {
#            print PIPE_HANDLE;
#        }
#        close PIPE_HANDLE;
#
#    #
#    #  Else use SMTPSERVER
#    #
#
#    } elsif ($ENV{'SMTPSERVER'}) {
#        &ade_err_debug($errstack_ref, 4, "ade_mua_sendmail: mailing with Net::SMTP to $ENV{'SMTPSERVER'} for " . join(' ', @$mailto_ref));
#        $smtp = Net::SMTP->new($ENV{'SMTPSERVER'});
#        #  this is about the safest definition of our own email address,
#        if (($rc=&ade_usr_getmyloginname($errstack_ref, \$user)) != $ade_err_ok) {
#            return($rc);
#        }
#        $smtp->mail($user . "\@[127.0.0.1]");
#        $smtp->to(@$mailto_ref);
#        $smtp->data();
#        $smtp->datasend(map { $$headers_ref{$_}{value} . "\n" } (sort { $$headers_ref{$a}{lineno} <=> $$headers_ref{$b}{lineno} } keys %$headers_ref));
#        $smtp->datasend("\n");
#        while (<$body_handle>) {
#            $smtp->datasend($_);
#        }
#        $smtp->dataend();
#        $smtp->quit;
#
#    #
#    #  Otherwise give up.
#    #
#
#    } else {
#        &ade_err_error($errstack_ref, ade_err_misc, "no deliver method available");
#        return($ade_err_fail);
#    }
#
#    
#    return($ade_err_ok);
#}
#
#sub ade_mua_parsemail
#{
#    my($errstack_ref, $letter_handle, $headers_ref, $body_handle) = @_;
#    my($header_name, $header_value, $finished_processing_headers);
#    my($last_header_line_was_received_leader, %new_headers);
#    
#    #  Put lines into 'new' headers and bodylines
#    $finished_processing_headers = 0;
#    %new_headers = ();
#    while (<$letter_handle>) {
#	chomp;
#
#        &ade_err_debug($errstack_ref, 4, "ade_mua_parsemail: line=[$_]");
#        ($header_name, $header_value) = split(/:\s+/, $_, 2);
#
#        if ($finished_processing_headers) {
#	    ;
#
#	} elsif ($header_name !~ /^(?:From|Reply-To|Date|Subject|Received|Keywords|X-Newsreader|Summary|Distribution|Message-Id|Return-Path|Newsgroups|References)$/) {
#            $finished_processing_headers = 1;
#        }
#
#        if ($finished_processing_headers) {
#            print $body_handle "$_\n";
#        } else {
#            &ade_mua_addmailheader(\%new_headers, $_);
#        }
#    }
#
#    &ade_mua_mergemailheaders($headers_ref, \%new_headers);
#    undef %new_headers;
#
#    return($ade_err_ok);
#}
#
#sub ade_mua_mergemailheaders
#{
#    my($headers_ref, $new_headers_ref) = @_;
#    my($new_header_key);
#    #my($new_bodyline);
#
#    #foreach $new_bodyline (@$new_bodylines_ref) {
#    #    push(@$bodylines_ref, $new_bodyline);
#    #}
#
#    foreach $new_header_key (keys %$new_headers_ref) {
#        $$headers_ref{$new_header_key} = $$new_headers_ref{$new_header_key};
#    }
#
#    return($ade_err_ok);
#}
#
################################################################################
##
##  PRIVATE FUNCTIONS 
##
################################################################################

def _ade_gep_main_withstack(errstack, app_main_ref):
    rc = _ade_gep_getosid(errstack)
    if rc != ade_err_ok:
        ade_err_error(errstack, 'ade_err_misc', "failed to determine OS")
        return rc

    rc = app_main_ref(errstack)
    if rc != ade_err_ok:
        ade_err_error(errstack, 'ade_err_misc', "application's entry function failed")
        return rc

    #
    #  If we get this far then everything is ok.
    #

    return ade_err_ok

def _ade_gep_exit(errstack, rc):
    _ade_tmp_cleanup(errstack)
    _ade_tmp_funcup(errstack)
    sys.exit(0 if rc == ade_err_ok else 1)

def _ade_gep_signalhandler(signum, frame):
    errstack = []

    ade_err_resetstack(errstack, stack=errstack)
    ade_err_info(errstack, "clearing up ...")
    _ade_gep_exit(errstack, 4)

def _ade_gep_getosid(errstack):
    global ade_unames, ade_unamer, ade_unamen, ade_unamem, ade_osid

    ade_unames = subprocess.Popen(["uname", "-s"], stdout=subprocess.PIPE).communicate()[0].rstrip()
    ade_unamer = subprocess.Popen(["uname", "-r"], stdout=subprocess.PIPE).communicate()[0].rstrip()
    ade_unamen = subprocess.Popen(["uname", "-n"], stdout=subprocess.PIPE).communicate()[0].rstrip()
    ade_unamem = subprocess.Popen(["uname", "-m"], stdout=subprocess.PIPE).communicate()[0].rstrip()
    if re.search('^i[3456]86$', ade_unamem):
        ade_unamem = "i386"
    if ade_unames != 'Linux':
         ade_osid = subprocess.Popen(["uname", "-sr"], stdout=subprocess.PIPE).communicate()[0].rstrip().replace(' /', '_')
    elif os.access("/etc/redhat-release", os.F_OK):
         subprocess.Popen(["cat", "/etc/redhat-release"], stdout=subprocess.PIPE).communicate()[0].rstrip()
         ade_osid = 'Linux_Redhat_' + subprocess.Popen(["uname", "-sr"], stdout=subprocess.PIPE).communicate()[0].rstrip().replace(' ', '_') + '_' + ade_unamem
    elif os.access("/etc/debian_version", os.F_OK):
        ade_osid = 'Linux_Debian_' + subprocess.Popen(["cat", "/etc/debian_version"], stdout=subprocess.PIPE).communicate()[0].rstrip() + '_' + ade_unamem
    else:
        ade_osid = 'Linux_Unknown_$ade_unamem'

    return ade_err_ok

def _ade_err_validate_errorkey(errstack, defined_errors_hash_key):
    global ade_registered_defined_errors

    if defined_errors_hash_key not in ade_registered_defined_errors:
        ade_err_internal(errstack, "%s: unknown error" % (defined_errors_hash_key))

    return ade_err_ok

def _ade_lck_validate_lockfile_contents(errstack, lockfile_contents):

    if not re.search('^[1-9][0-9]*\n$', lockfile_contents):
        return ade_err_fail
    return ade_err_ok

def _ade_msg_message(errstack, level, template, message):
    global ade_msg_verboselevel, ade_err_writerfunc_refs

    if level > ade_msg_verboselevel:
        return(ade_err_ok)

    template = template.replace('%MESSAGE', message)
    template = template.replace('%LEVEL', str(level))
    
    for writerfunc_ref in ade_err_writerfunc_refs:
        writerfunc_ref(errstack, template)

    return ade_err_ok

def _ade_tmp_register(errstack, listname, item):
    global ade_registers
    ade_registers[listname][item] = 1
    return ade_err_ok

def _ade_tmp_deregister(errstack, listname, item):
    global ade_registers
    try:
        del ade_registers[listname][item]
    except KeyError:
        pass
    return ade_err_ok

def _ade_tmp_deregisterallfunc(errstack):
    return _ade_tmp_deregisterall(errstack, 'callonexit')

def _ade_tmp_deregisterallfile(errstack):
    return _ade_tmp_deregisterall(errstack, 'delonexit')

def _ade_tmp_deregisterall(errstack, listname):
    global ade_registers
    try:
        for item in ade_registers[listname].keys():
            _ade_tmp_deregister(errstack, listname, item)
    except KeyError:
        pass

    return ade_err_ok

def _ade_tmp_cleanup(errstack):
    global ade_registers

    ade_err_debug(errstack, 10, '_ade_tmp_cleanup: sof')
    os.chdir('/')
    subprocess.call(["rm", "-fr"] + ade_registers['delonexit'].keys())
    return _ade_tmp_deregisterallfile(errstack)
    
def _ade_tmp_funcup(errstack):
    global ade_registers

    ade_err_debug(errstack, 10, '_ade_tmp_funcup: sof')
    try:
        for item in ade_registers['callonexit'].keys():
            item()
    except KeyError:
        pass
    return _ade_tmp_deregisterallfunc(errstack)

def _ade_err_displayinternal(errstack, message):
    return _ade_msg_message(errstack, 0, "INTERNAL ERROR: %MESSAGE", message)

def _ade_err_displayerror(errstack, message):
    return _ade_msg_message(errstack, 1, "ERROR: %MESSAGE", message)

def _ade_err_displaywarning(errstack, message):
    return _ade_msg_message(errstack, 2, "WARNING: %MESSAGE", message)

def _ade_err_displayinfo(errstack, message):
    return _ade_msg_message(errstack, 3, "INFO: %MESSAGE", message)

def _ade_err_displaydebug(errstack, level, message):
    return _ade_msg_message(errstack, level, "DEBUG[%LEVEL]: %MESSAGE", message)

def _ade_msg_writer_stderr(errstack_ref, text):
    try:
        sys.stderr.write("%s: %s\n" % (ade_app_progname, text))
    except IOError:
        pass
    return ade_err_ok

#MISSING: _ade_msg_writer_logfile
#sub _ade_msg_writer_logfile
#{
#    my($errstack_ref, $text) = @_;
#
#    return if (!defined($ade_msg_writer_logfile_filename));
#
#    open(HANDLE, ">>$ade_msg_writer_logfile_filename");
#    #  The 'eval { ... }' is there *only* to provide some grouping
#    #  on the elements "5, 4, 3, 2, 1, 0, $text"; otherwise 'map'
#    #  greedily eats all of that array leaving no string for printf
#    #  to match against the '%s'.
#    printf HANDLE "%04d/%02d/%02dT%02d:%02d:%02d: %s\n", eval { map { (localtime)[$_]+(0,0,0,0,1,1900)[$_] } (5,4,3,2,1,0) }, $text;
#    close HANDLE;
#
#    return($ade_err_ok);
#}

#MISSING: _ade_msg_writer_syslog

#MISSING: _ade_msg_writer_null

def _ade_gep_initialise_with_stack(errstack):
    global ade_opt_want_to_show_paths, ade_opt_want_to_show_version, ade_opt_want_to_show_usage, ade_msg_verboselevel

    ade_opt_want_to_show_paths = 0
    ade_opt_want_to_show_version = 0
    ade_opt_want_to_show_usage = 0
    ade_msg_verboselevel = 2

    #  Register the options ADE will handle (and the functions to handle them).
    ade_err_debug(errstack, 10, "_ade_gep_initialise_with_stack: calling ade_opt_register() to register ADE options ...")
    rc = ade_opt_register(errstack, "Vd:ivhp",  "version,debug:i,verbose,help,list-paths", globals(), "_ade_opt_handler_%s")
    if rc != ade_err_ok:
        ade_err_error(errstack, 'ade_err_misc', [ "ade_opt_register() call to register ADE options failed" ])
        return rc
    
    return ade_err_ok

def _ade_opt_handler_V(errstack):
    return(_ade_opt_handler_version(errstack))

def _ade_opt_handler_p(errstack):
    return(_ade_opt_handler_list_paths(errstack))

def _ade_opt_handler_h(errstack):
    return(_ade_opt_handler_help(errstack))

def _ade_opt_handler_d(errstack, verboselevel):
    return(_ade_opt_handler_debug(errstack, verboselevel))

def _ade_opt_handler_v(errstack):
    return(_ade_opt_handler_verbose(errstack))

def _ade_opt_handler_version(errstack):
    global ade_opt_want_to_show_version
    ade_opt_want_to_show_version = 1
    return ade_err_ok

def _ade_opt_handler_help(errstack):
    global ade_opt_want_to_show_usage
    ade_opt_want_to_show_usage = 1
    return ade_err_ok

def _ade_opt_handler_list_paths(errstack):
    global ade_opt_want_to_show_paths
    ade_opt_want_to_show_paths = 1
    return ade_err_ok

def _ade_opt_handler_debug(errstack, verboselevel):
    global ade_msg_verboselevel
    ade_err_debug(errstack, 10, "_ade_opt_handler_debug: setting ade_msg_verboselevel to %s ..." % (verboselevel))
    ade_msg_verboselevel = verboselevel
    return ade_err_ok

def _ade_opt_handler_verbose(errstack):
    global ade_msg_verboselevel
    ade_msg_verboselevel = 3
    return ade_err_ok

def _ade_gep_initialise_without_stack():
    if 'CDPATH' in os.environ: 
        del os.environ['CDPATH']
    
    if 'TMPDIR' in os.environ  and os.environ['TMPDIR'] and os.path.isdir(os.environ['TMPDIR']):
        ade_tmp_dir = os.environ['TMPDIR']
    elif 'TMP_DIR' in os.environ and os.environ['TMP_DIR'] and os.path.isdir(os.environ['TMP_DIR']):
        ade_tmp_dir = os.environ['TMP_DIR']
    elif os.path.isdir("/var/tmp"):
        ade_tmp_dir = "/var/tmp"

    #  There is no point in passing an error stack to ade_err_registerdefderrs(), 
    #  because if it failed then it cannot report the error since no error formats
    #  have been successfully register.
    ade_err_registerdefderrs(ade_defined_errors)

    #  Set up auto-clean-up.
    signal.signal(signal.SIGHUP, _ade_gep_signalhandler)
    signal.signal(signal.SIGINT, _ade_gep_signalhandler)
    signal.signal(signal.SIGTERM, _ade_gep_signalhandler)

# ade_err_displaystack definition delayed to avoid forward reference (see above)
def ade_err_displaystack(errstack, displayfunc_ref=_ade_err_displayerror):
    global ade_registered_defined_errors

#    #
#    #  Check/sanitize parameters.
#    #
#
#    #  There is a default display function based on assumption most calls to ade_err_displaystack
#    #  are because of errors (not warnings).
#    if (!defined($displayfunc_ref)) {
#        $displayfunc_ref = \&_ade_err_displayerror;
#    }

    #
    #  For each error frame on the stack  ...
    #

    for i in range(len(errstack)):
        #  Skip all but the top frame if requested so to do
        ade_err_debug(errstack, 10, 'ade_err_displaystack: i=%d, ade_err_dumpwholestack=%d' % (i, ade_err_dumpwholestack))
        if i != 0 and ade_err_dumpwholestack == 0:
            #ade_err_debug(errstack, 10, 'ade_err_displaystack: should skip this frame!')
            continue

        ade_err_debug(errstack, 10, "ade_err_displaystack: frame is %s" % (errstack[i]))
        if errstack[i]["err"] not in ade_registered_defined_errors:
	    ade_err_internal(errstack, "%s: unknown error" % (errstack[i][err]))
 	#print ade_registered_defined_errors[errstack[i]["err"]]["fmt"]
 	#print errstack[i]["par"]
        error_message = ade_registered_defined_errors[errstack[i]["err"]]["fmt"] % tuple(errstack[i]["par"])
 	#print "ERRMSG: " + error_message
        displayfunc_ref(errstack, ("", "frame#" + str(i) + ": ")[ade_err_dumpwholestack] + error_message)

    return ade_err_ok

##############################################################################
#
#  CODE
#
##############################################################################

# ade_err_writerfunc_refs initialisation delayed to avoid forward reference (see above)
ade_err_writerfunc_refs = [ _ade_msg_writer_stderr ] 
_ade_gep_initialise_without_stack()
