[BACK]Return to mlog.c CVS log [TXT][DIR] Up to [Development] / xfs-cmds / xfsdump / common

File: [Development] / xfs-cmds / xfsdump / common / mlog.c (download)

Revision 1.16, Thu Feb 1 19:02:45 2007 UTC (10 years, 8 months ago) by wkendall
Branch: MAIN
Changes since 1.15: +5 -5 lines

xfsdump uses the optopt variable from getopt incorrectly. It assumes
that the value is set to the current option being processed, when
in fact it is only set when getopt encounters an unknown option.

Thanks to Kouta Ooizumi.

/*
 * Copyright (c) 2000-2001 Silicon Graphics, Inc.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <xfs/xfs.h>
#include <xfs/jdm.h>

#include <sys/types.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <getopt.h>

#include "types.h"
#include "qlock.h"
#include "stream.h"
#include "mlog.h"
#include "cldmgr.h"
#include "getopt.h"
#include "exit.h"
#include "util.h"
#include "global.h"
#include "drive.h"

extern char *progname;
extern void usage( void );
extern pid_t parentpid;

#ifdef DUMP
static FILE *mlog_fp = NULL; /* stderr */;
#endif /* DUMP */
#ifdef RESTORE
static FILE *mlog_fp = NULL; /* stdout */;
#endif /* RESTORE */

intgen_t mlog_level_ss[ MLOG_SS_CNT ];

intgen_t mlog_showlevel = BOOL_FALSE;

intgen_t mlog_showss = BOOL_FALSE;

intgen_t mlog_timestamp = BOOL_FALSE;

static intgen_t mlog_sym_lookup( char * );

static size_t mlog_streamcnt;

static char mlog_levelstr[ 3 ]; 

#define MLOG_SS_NAME_MAX	15
#ifdef DUMP
#define PROGSTR "dump"
#define PROGSTR_CAPS "Dump"
#else
#define PROGSTR "restore"
#define PROGSTR_CAPS "Restore"
#endif /* DUMP */
#define N(a) (sizeof((a)) / sizeof((a)[0]))

static char mlog_ssstr[ MLOG_SS_NAME_MAX + 2 ];

static char mlog_tsstr[ 10 ];

struct mlog_sym {
	char *sym;
	intgen_t level;
};

typedef struct mlog_sym mlog_sym_t;

char *mlog_ss_names[ MLOG_SS_CNT ] = {
	"general",	/* MLOG_SS_GEN */
	"proc",		/* MLOG_SS_PROC */
	"drive",	/* MLOG_SS_DRIVE */
	"media",	/* MLOG_SS_MEDIA */
	"inventory",	/* MLOG_SS_INV */
#ifdef DUMP
	"inomap",	/* MLOG_SS_INOMAP */
#endif /* DUMP */
#ifdef RESTORE
	"tree",		/* MLOG_SS_TREE */
#endif /* RESTORE */
	"excluded_files" /* MLOG_SS_EXCLFILES */
};

static mlog_sym_t mlog_sym[ ] = {
	{"0",		MLOG_SILENT},
	{"1",		MLOG_VERBOSE},
	{"2",		MLOG_TRACE},
	{"3",		MLOG_DEBUG},
	{"4",		MLOG_NITTY},
	{"5",		MLOG_NITTY + 1},
	{"silent",	MLOG_SILENT},
	{"verbose",	MLOG_VERBOSE},
	{"trace",	MLOG_TRACE},
	{"debug",	MLOG_DEBUG},
	{"nitty",	MLOG_NITTY}
};

static qlockh_t mlog_qlockh;
static int mlog_main_exit_code = -1;
static rv_t mlog_main_exit_return = RV_NONE;
static rv_t mlog_main_exit_hint = RV_NONE;

bool_t
mlog_init1( intgen_t argc, char *argv[ ] )
{
	char **suboptstrs;
	ix_t ssix;
	ix_t soix;
	size_t vsymcnt;
	intgen_t c;

#ifdef DUMP
        mlog_fp = stderr;
#endif /* DUMP */
#ifdef RESTORE
        mlog_fp = stdout;
#endif /* RESTORE */

	/* initialize stream count. will be updated later by call to
	 * mlog_tell_streamcnt( ), after drive layer has counted drives
	 */
	mlog_streamcnt = 1;

	/* prepare an array of suboption token strings. this will be the
	 * concatenation of the subsystem names with the verbosity symbols.
	 * this array of char pts must be null terminated for getsubopt( 3 ).
	 */
	vsymcnt = sizeof( mlog_sym ) / sizeof( mlog_sym[ 0 ] );
	suboptstrs = ( char ** )calloc( MLOG_SS_CNT + vsymcnt + 1,
					sizeof( char * ));
	ASSERT( suboptstrs );
	for ( soix = 0 ; soix < MLOG_SS_CNT ; soix++ ) {
		ASSERT( strlen( mlog_ss_names[ soix ] ) <= MLOG_SS_NAME_MAX );
			/* unrelated, but opportunity to chk */
		suboptstrs[ soix ] = mlog_ss_names[ soix ];
	}
	for ( ; soix < MLOG_SS_CNT + vsymcnt ; soix++ ) {
		suboptstrs[ soix ] = mlog_sym[ soix - MLOG_SS_CNT ].sym;
	}
	suboptstrs[ soix ] = 0;

	/* set all of the subsystem log levels to -1, so we can see which
	 * subsystems where explicitly called out. those which weren't will
	 * be given the "general" level.
	 */
	for ( ssix = 0 ; ssix < MLOG_SS_CNT ; ssix++ ) {
		mlog_level_ss[ ssix ] = -1;
	}
	mlog_level_ss[ MLOG_SS_GEN ] = MLOG_VERBOSE;

	/* get command line options
	 */
	optind = 1;
	opterr = 0;
	while ( ( c = getopt( argc, argv, GETOPT_CMDSTRING )) != EOF ) {
		char *options;

		switch ( c ) {
		case GETOPT_VERBOSITY:
			if ( ! optarg || optarg[ 0 ] == '-' ) {
				fprintf( stderr,
					 _("%s: -%c argument missing\n"),
					 progname,
					 c );
				usage( );
				return BOOL_FALSE;
			}
			options = optarg;
			while ( *options ) {
				intgen_t suboptix;
				char *valstr;

				suboptix = getsubopt( &options, 
						      (constpp)suboptstrs,
						      &valstr );
				if ( suboptix < 0 ) {
					fprintf( stderr,
						 _("%s: -%c argument invalid\n"),
						 progname,
						 c );
					usage( );
					return BOOL_FALSE;
				}
				ASSERT( ( ix_t )suboptix
					<
					MLOG_SS_CNT + vsymcnt );
				if ( suboptix < MLOG_SS_CNT ) {
					if ( ! valstr ) {
						fprintf( stderr,
							 _("%s: -%c subsystem "
							 "subargument "
							 "%s requires a "
							 "verbosity value\n"),
							 progname,
							 c,
						mlog_ss_names[ suboptix ] );
						usage( );
						return BOOL_FALSE;
					}
					ssix = ( ix_t )suboptix;
					mlog_level_ss[ ssix ] =
						    mlog_sym_lookup( valstr );
				} else {
					if ( valstr ) {
						fprintf( stderr,
							 _("%s: -%c argument "
							 "does not require "
							 "a value\n"),
							 progname,
							 c );
						usage( );
						return BOOL_FALSE;
					}
					ssix = MLOG_SS_GEN;
					mlog_level_ss[ ssix ] =
				    mlog_sym_lookup( suboptstrs[ suboptix ] );
				}
				if ( mlog_level_ss[ ssix ] < 0 ) {
					fprintf( stderr,
						 _("%s: -%c argument "
						 "invalid\n"),
						 progname,
						 c );
					usage( );
					return BOOL_FALSE;
				}
			}
			break;
		case GETOPT_SHOWLOGLEVEL:
			mlog_showlevel = BOOL_TRUE;
			break;
		case GETOPT_SHOWLOGSS:
			mlog_showss = BOOL_TRUE;
			break;
		case GETOPT_TIMESTAMP:
			mlog_timestamp = BOOL_TRUE;
			break;
		}
	}

	free( ( void * )suboptstrs );

	/* give subsystems not explicitly called out the "general" verbosity
	 */
	for ( ssix = 0 ; ssix < MLOG_SS_CNT ; ssix++ ) {
		if ( mlog_level_ss[ ssix ] < 0 ) {
			ASSERT( mlog_level_ss[ ssix ] == -1 );
			ASSERT( mlog_level_ss[ MLOG_SS_GEN ] >= 0 );
			mlog_level_ss[ ssix ] = mlog_level_ss[ MLOG_SS_GEN ];
		}
	}

	/* prepare a string for optionally displaying the log level
	 */
	mlog_levelstr[ 0 ] = 0;
	mlog_levelstr[ 1 ] = 0;
	mlog_levelstr[ 2 ] = 0;
	if ( mlog_showlevel ) {
		mlog_levelstr[ 0 ] = ':';
	}

#ifdef DUMP
	/* note if dump going to stdout. if so, can't
	 * send mlog output there. since at compile time
	 * mlog_fd set to stderr, see if we can switch
	 * to stdout.
	 */
	if ( optind >= argc ||  strcmp( argv[ optind ], "-" )) {
		mlog_fp = stdout;
	}
#endif /* DUMP */

	mlog_qlockh = QLOCKH_NULL;

	return BOOL_TRUE;
}

bool_t
mlog_init2( void )
{
	/* allocate a qlock
	 */
	mlog_qlockh = qlock_alloc( QLOCK_ORD_MLOG );

	return BOOL_TRUE;
}

void
mlog_tell_streamcnt( size_t streamcnt )
{
	mlog_streamcnt = streamcnt;
}

void
mlog_lock( void )
{
	qlock_lock( mlog_qlockh );
}

void
mlog_unlock( void )
{
	qlock_unlock( mlog_qlockh );
}

/*
 * Override the -v option.
 * Useful for debugging at particular points
 * where doing it program-wide would produce
 * too much output.
 */
void
mlog_override_level( intgen_t levelarg )
{
	intgen_t level;
	ix_t ss; /* SubSystem */

	level = levelarg & MLOG_LEVELMASK;
	ss = ( ix_t )( ( levelarg & MLOG_SS_MASK ) >> MLOG_SS_SHIFT );

	if (ss == MLOG_SS_GEN) { /* do level for all subsys */  
	    for (ss = 0 ; ss < MLOG_SS_CNT ; ss++ ) {
		mlog_level_ss[ ss ] = level;
	    }
	}
	else { /* do a particular subsys */
	    mlog_level_ss[ ss ] = level;
	}
}

void
mlog( intgen_t levelarg, char *fmt, ... )
{
	va_list args;
	va_start( args, fmt );
	mlog_va( levelarg, fmt, args );
	va_end( args );
}

void
mlog_va( intgen_t levelarg, char *fmt, va_list args )
{
	intgen_t level;
	ix_t ss;

	level = levelarg & MLOG_LEVELMASK;
	ss = ( ix_t )( ( levelarg & MLOG_SS_MASK ) >> MLOG_SS_SHIFT );

	ASSERT( ss < MLOG_SS_CNT );
	if ( level > mlog_level_ss[ ss ] ) {
		return;
	}
	
	if ( ! ( levelarg & MLOG_NOLOCK )) {
		mlog_lock( );
	}

	if ( ! ( levelarg & MLOG_BARE )) {
		intgen_t streamix;
		streamix = stream_getix( getpid() );

		if ( mlog_showss ) {
			sprintf( mlog_ssstr, ":%s", mlog_ss_names[ ss ] );
		} else {
			mlog_ssstr[ 0 ] = 0;
		}

		if ( mlog_timestamp ) {
			time_t now = time( 0 );
			struct tm *tmp = localtime( &now );
			sprintf( mlog_tsstr,
				 ":%02d.%02d.%02d",
				 tmp->tm_hour,
				 tmp->tm_min,
				 tmp->tm_sec );
			ASSERT( strlen( mlog_tsstr ) < sizeof( mlog_tsstr ));
		} else {
			mlog_tsstr[ 0 ] = 0;
		}

		if ( mlog_showlevel ) {
			mlog_levelstr[ 0 ] = ':';
			if ( level > 9 ) {
				mlog_levelstr[ 1 ] = '?';
			} else {
				mlog_levelstr[ 1 ] = ( char )
						     ( level
						       +
						       ( intgen_t )'0' );
			}
		} else {
			mlog_levelstr[ 0 ] = 0;
		}
		if ( streamix != -1 && mlog_streamcnt > 1 ) {
			fprintf( mlog_fp,
				 _("%s%s%s%s: drive %d: "),
				 progname,
				 mlog_tsstr,
				 mlog_ssstr,
				 mlog_levelstr,
				 streamix );
		} else {
			fprintf( mlog_fp,
				 "%s%s%s%s: ",
				 progname,
				 mlog_tsstr,
				 mlog_ssstr,
				 mlog_levelstr );
		}
		if ( levelarg & MLOG_NOTE ) {
			fprintf( mlog_fp,
				 "NOTE: " );
		}
		if ( levelarg & MLOG_WARNING ) {
			fprintf( mlog_fp,
				 "WARNING: " );
		}
		if ( levelarg & MLOG_ERROR ) {
			fprintf( mlog_fp,
				 "ERROR: " );
		}
	}

	vfprintf( mlog_fp, fmt, args );
	fflush( mlog_fp );

	if ( ! ( levelarg & MLOG_NOLOCK )) {
		mlog_unlock( );
	}
}


static const char *exit_strings[] =
	{ "SUCCESS", "ERROR", "INTERRUPT", "", "FAULT" };


/*
 * Map RV codes to actual error messages.
 */

struct rv_map {
	int		rv;
	const char *	rv_string;
	const char *	rv_desc;
};

static struct rv_map
rvs[_RV_NUM] = {
       /* Return Code	Displayed Code	Explanation */
	{ RV_OK,	"OK",		"success" }, 
	{ RV_NOTOK,	"ERASE_FAILED",	"media erase request denied" },
	{ RV_NOMORE,	"NOMORE",	"no more work to do" },
	{ RV_EOD,	"EOD",		"ran out of data" },
	{ RV_EOF,	"EOF",		"hit end of media file" },
 	{ RV_EOM,	"EOM",		"hit end of media" },
	{ RV_ERROR,	"ERROR",	"operator error or resource exhaustion" },
	{ RV_DONE,	"ALREADY_DONE",	"another stream completed the operation" },
	{ RV_INTR,	"INTERRUPT",	PROGSTR " interrupted" },
	{ RV_CORRUPT,	"CORRUPTION",	"corrupt data encountered" },
	{ RV_QUIT,	"QUIT",		"media is no longer usable" },
	{ RV_DRIVE,	"DRIVE_ERROR",	"drive error" },
	{ RV_TIMEOUT,	"TIMEOUT",	"operation timed out" },
	{ RV_MEDIA,	"NO_MEDIA",	"no media in drive" },
	{ RV_PROTECTED,	"WRITE_PROTECTED","object write protected" },
	{ RV_CORE,	"CORE",		"fatal error - core dumped" },
	{ RV_OPT,	"OPT_ERROR",	"bad command line option" },
	{ RV_INIT,	"INIT_ERROR",	"could not initialise subsystem" },
	{ RV_PERM,	"NO_PERMISSION","insufficient privilege" },
	{ RV_COMPAT,	"INCOMPATIBLE",	"cannot apply - dump incompatible" },
	{ RV_INCOMPLETE,"INCOMPLETE",	"the " PROGSTR " is incomplete" },
	{ RV_KBD_INTR,	"KEYBOARD_INTERRUPT", "keyboard interrupt" },
	{ RV_INV,	"INVENTORY",	"error updating session inventory" },
	{ RV_USAGE,	"USAGE_ONLY",	"printing usage only" },
	{ RV_EXISTS,	"EXISTS",	"file or directory already exists" },
	{ RV_NONE,	"NONE",		"no error code" },
	{ RV_UNKNOWN,	"UNKNOWN",	"unknown error" },
};

static struct rv_map 
rv_unknown = {
	  _RV_NUM,	"???",		"unknown error code"
};

static const struct rv_map *
rv_getdesc(rv_t rv)
{
	int rvidx;

	if (rv < 0 || rv >= _RV_NUM) {
		return &rv_unknown;
	}

	for (rvidx = 0; rvidx < _RV_NUM; rvidx++)
		if (rv == rvs[rvidx].rv)
			return &rvs[rvidx];

	return &rv_unknown;
}


/*
 * mlog_exit takes two arguments an exit code (EXIT_*) and the internal
 * return code (RV_*) that was signalled prior to the exit. mlog_exit
 * stores these values in a per-stream structure managed by the stream_*
 * functions.
 *
 * mlog_exit is called for: all returns from the content threads
 * (content_stream_dump and content_stream_restore); for returns from
 * the main process; and also from a couple of other locations where an
 * error is known to directly lead to the termination of the program.
 *
 * For example, in the places mentioned above "return EXIT_ERROR;" has
 * now been replaced with a call like
 * "return mlog_exit(EXIT_ERROR, RV_DRIVE);" that specifies both the exit
 * code, and the reason why the program is terminating.
 *
 * mlog_exit_flush uses the per-stream exit information recorded using
 * mlog_exit to print a detailed status report, showing both the exit
 * status of each stream, and the overall exit status of the
 * program. This additional log information allows the user to detect
 * failures that cannot be distinguished by looking at the exit code
 * alone.  In particular, the exit code does not currently allow the
 * user to distinguish EOM conditions from user interruptions, and to
 * detect an incomplete dump (caused, for example, by drive errors or
 * corruption).
 *
 * Note, that to maintain backwards compatibility the exit codes
 * returned by dump/restore have _not_ been changed. For more
 * information see PV #784355.
 *
 * While mlog_exit provides considerably more information about the
 * reasons for a dump terminating, there are a number of cases where
 * dump maps return codes that have specific values such as RV_DRIVE,
 * to return codes with less specific values such as RV_INTR, and in
 * doing so throws away information that would have been useful in
 * diagnosing the reasons for a failure. To alleviate this, an
 * additional function mlog_exit_hint is provided that allows a "hint"
 * to be made about the real reason a stream is terminating. A call to
 * mlog_exit_hint should be made anywhere in the code a change in
 * program state has occured that might lead to the termination of the
 * dump. The mlog_exit_flush routine uses this additional information
 * help work out what really happened.
 */

int
_mlog_exit( const char *file, int line, int exit_code, rv_t rv )
{
	pid_t pid;
	const struct rv_map *rvp;

	pid = getpid();
	rvp = rv_getdesc(rv);


	mlog( MLOG_DEBUG | MLOG_NOLOCK,
	      "%s: %d: mlog_exit called: "
	      "exit_code: %s return: %s (%s)\n",
	      file, line,
	      exit_strings[exit_code],
	      rvp->rv_string, rvp->rv_desc);

	if (rv < 0 || rv >= _RV_NUM) {
		mlog( MLOG_DEBUG | MLOG_ERROR | MLOG_NOLOCK,
		      "mlog_exit(): bad return code");
		return exit_code;
	}

	/*
	 * NOTE: we record the first call only. Exit codes can be mapped from
	 * more specific values to less specific values as we return up the
	 * call chain. We assume therefore that the first call contains the
	 * most accurate information about the termination condition.
	 */

	if (pid == parentpid) {
		if (mlog_main_exit_code == -1) {
			mlog_main_exit_code = exit_code;
			mlog_main_exit_return = rv;
		}
	}
	else {
		stream_state_t states[] = { S_RUNNING };
		stream_state_t state;
		intgen_t streamix;
		int exit_code;
		rv_t exit_return, exit_hint;

		if (stream_get_exit_status(pid,
					   states,
					   N(states),
					   &state,
					   &streamix,
					   &exit_code,
					   &exit_return,
					   &exit_hint))
		{
			if (exit_code == -1) {
				stream_set_code(pid, exit_code);
				stream_set_return(pid, rv);
			}
		}
	}

	return exit_code;
}

void
_mlog_exit_hint( const char *file, int line, rv_t rv )
{
	pid_t pid;
	const struct rv_map *rvp;

	pid = getpid();
	rvp = rv_getdesc(rv);
	
	mlog( MLOG_DEBUG | MLOG_NOLOCK,
	      "%s: %d: mlog_exit_hint called: "
	      "hint: %s (%s)\n",
	      file, line,
	      rvp->rv_string, rvp->rv_desc);

	if (rv < 0 || rv >= _RV_NUM) {
		mlog( MLOG_DEBUG | MLOG_ERROR | MLOG_NOLOCK,
		      "mlog_exit_hint(): bad return code");
		return;
	}

	/*
	 * NOTE: we use the last hint before exit. Unlike exit codes we've added
	 * calls to mlog_exit_hint to improve error reporting. In general the
	 * call closest to the final exit point will provide the most accurate
	 * information about the termination condition.
	 */

	if (pid == parentpid)
		mlog_main_exit_hint = rv;
	else 
		stream_set_hint( pid, rv );

}

rv_t
mlog_get_hint( void )
{
	stream_state_t states[] = { S_RUNNING };
	/* REFERENCED */
	bool_t ok;
	rv_t hint;

	if (getpid() == parentpid)
		return mlog_main_exit_hint;

	ok = stream_get_exit_status(getpid(), states, N(states),
				    NULL, NULL, NULL, NULL, &hint);
	ASSERT(ok);
	return hint;
}


#define IS_INCOMPLETE(rv)			\
	((rv) == RV_CORRUPT ||			\
	 (rv) == RV_INCOMPLETE ||		\
	 (rv) == RV_EOD ||			\
	 (rv) == RV_EOF ||			\
	 (rv) == RV_EOM)

#define VALID_EXIT_CODE(code)			\
	((code) == EXIT_NORMAL ||		\
	 (code) == EXIT_ERROR ||		\
	 (code) == EXIT_INTERRUPT ||		\
	 (code) == EXIT_FAULT)


void
mlog_exit_flush(void)
{
	pid_t pids[STREAM_SIMMAX];
	int i, npids;
	const struct rv_map *rvp;
	stream_state_t states[] = { S_RUNNING, S_ZOMBIE };
	bool_t incomplete = BOOL_FALSE;
	bool_t quit = BOOL_FALSE;
	bool_t interrupt = BOOL_FALSE;
	const char *status_str;
	rv_t rv;

	if (mlog_level_ss[MLOG_SS_GEN] == MLOG_SILENT)
		return;

	if (mlog_main_exit_hint == RV_USAGE)
		return;

	npids = stream_find_all(states, N(states), pids, STREAM_SIMMAX);
	if (npids > 0) {

		/* print the state of all the streams */
		fprintf(mlog_fp, _("%s: %s Summary:\n"), progname, PROGSTR_CAPS );

		for (i = 0; i < npids; i++) {
			stream_state_t state;
			intgen_t streamix;
			int exit_code;
			rv_t exit_return, exit_hint;
			/* REFERENCED */
			bool_t ok;

			ok = stream_get_exit_status(pids[i],
						    states,
						    N(states),
						    &state,
						    &streamix,
						    &exit_code,
						    &exit_return,
						    &exit_hint);
			ASSERT(ok);

			/* hint takes priority over return */
			rv = (exit_hint != RV_NONE) ? exit_hint : exit_return;

			/* print status of this stream */
			rvp = rv_getdesc(rv);
			fprintf(mlog_fp,
				_("%s:   stream %d (pid %d) %s "
				"%s (%s)\n"),
				progname,
				streamix,
				pids[i],
				drivepp[streamix]->d_pathname,
				rvp->rv_string,
				rvp->rv_desc);

			/* If the following conditions are true for any stream
			 * then they are true for the entire dump/restore.
			 */
			if (rv == RV_INTR) interrupt = BOOL_TRUE;
			else if (rv == RV_QUIT) quit = BOOL_TRUE;
			else if (IS_INCOMPLETE(rv)) incomplete = BOOL_TRUE;
		}
	}

	/* Also check return codes for the main process
	 */
	rv = (mlog_main_exit_hint != RV_NONE) ? mlog_main_exit_hint
	    : mlog_main_exit_return;

	if (rv == RV_INTR) interrupt = BOOL_TRUE;
	else if (rv == RV_QUIT) quit = BOOL_TRUE;
	else if (IS_INCOMPLETE(rv)) incomplete = BOOL_TRUE;

	/* if we don't have an exit code here there is a problem */
	ASSERT(VALID_EXIT_CODE(mlog_main_exit_code));
	if (interrupt) status_str = "INTERRUPT";
	else if (quit) status_str = "QUIT";
	else if (incomplete) status_str = "INCOMPLETE";
#ifdef NDEBUG
	/* We should never get here, but if we do make sure we don't die
	   horribly when not running debug. */
	else if (! VALID_EXIT_CODE(mlog_main_exit_code)) status_str = "UNKNOWN";
#endif /* NDEBUG */
	else status_str = exit_strings[mlog_main_exit_code];

	/* now print the overall state of the dump/restore */
	fprintf(mlog_fp, "%s: %s Status: %s\n", progname, PROGSTR_CAPS, status_str);
	fflush(mlog_fp);
}

static intgen_t
mlog_sym_lookup( char *sym )
{
	mlog_sym_t *p = mlog_sym;
	mlog_sym_t *ep = mlog_sym
			 +
			 sizeof( mlog_sym ) / sizeof( mlog_sym[ 0 ] );

	for ( ; p < ep ; p++ ) {
		if ( ! strcmp( sym, p->sym )) {
			return p->level;
		}
	}

	return -1;
}