| /* |
| * 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 <sys/types.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <getopt.h> |
| #include <pthread.h> |
| #include <assert.h> |
| #include <string.h> |
| #include <uuid/uuid.h> |
| |
| #include "config.h" |
| |
| #include "types.h" |
| #include "qlock.h" |
| #include "stream.h" |
| #include "mlog.h" |
| #include "cldmgr.h" |
| #include "getopt.h" |
| #include "exit.h" |
| #include "global.h" |
| #include "drive.h" |
| |
| extern char *progname; |
| extern void usage( void ); |
| extern pthread_t parenttid; |
| |
| #ifdef DUMP |
| static FILE *mlog_fp = NULL; /* stderr */; |
| #endif /* DUMP */ |
| #ifdef RESTORE |
| static FILE *mlog_fp = NULL; /* stdout */; |
| #endif /* RESTORE */ |
| |
| int mlog_level_ss[ MLOG_SS_CNT ]; |
| |
| int mlog_showlevel = BOOL_FALSE; |
| |
| int mlog_showss = BOOL_FALSE; |
| |
| int mlog_timestamp = BOOL_FALSE; |
| |
| static int 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; |
| int 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; |
| |
| void |
| mlog_init0( void ) |
| { |
| int i; |
| |
| #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; |
| |
| for( i = 0 ; i < MLOG_SS_CNT ; i++ ) { |
| mlog_level_ss[ i ] = MLOG_VERBOSE; |
| } |
| } |
| |
| bool_t |
| mlog_init1( int argc, char *argv[ ] ) |
| { |
| char **suboptstrs; |
| ix_t ssix; |
| ix_t soix; |
| size_t vsymcnt; |
| int c; |
| |
| /* 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 ) { |
| int 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( int levelarg ) |
| { |
| int 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( int levelarg, char *fmt, ... ) |
| { |
| va_list args; |
| va_start( args, fmt ); |
| mlog_va( levelarg, fmt, args ); |
| va_end( args ); |
| } |
| |
| void |
| mlog_va( int levelarg, char *fmt, va_list args ) |
| { |
| int 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 )) { |
| int streamix; |
| streamix = stream_getix( pthread_self( ) ); |
| |
| 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 |
| + |
| ( int )'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( ); |
| } |
| } |
| |
| /* |
| * 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 ) |
| { |
| pthread_t tid; |
| const struct rv_map *rvp; |
| |
| tid = pthread_self(); |
| rvp = rv_getdesc(rv); |
| |
| |
| mlog( MLOG_DEBUG | MLOG_NOLOCK, |
| "%s: %d: mlog_exit called: " |
| "exit_code: %s return: %s (%s)\n", |
| file, line, |
| exit_codestring(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 ( pthread_equal( tid, parenttid ) ) { |
| 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; |
| int streamix; |
| int exit_code; |
| rv_t exit_return, exit_hint; |
| |
| if (stream_get_exit_status(tid, |
| states, |
| N(states), |
| &state, |
| &streamix, |
| &exit_code, |
| &exit_return, |
| &exit_hint)) |
| { |
| if (exit_code == -1) { |
| stream_set_code(tid, exit_code); |
| stream_set_return(tid, rv); |
| } |
| } |
| } |
| |
| return exit_code; |
| } |
| |
| void |
| _mlog_exit_hint( const char *file, int line, rv_t rv ) |
| { |
| pthread_t tid; |
| const struct rv_map *rvp; |
| |
| tid = pthread_self(); |
| 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 ( pthread_equal( tid, parenttid ) ) |
| mlog_main_exit_hint = rv; |
| else |
| stream_set_hint( tid, rv ); |
| |
| } |
| |
| rv_t |
| mlog_get_hint( void ) |
| { |
| stream_state_t states[] = { S_RUNNING }; |
| /* REFERENCED */ |
| bool_t ok; |
| rv_t hint; |
| |
| if ( pthread_equal( pthread_self(), parenttid ) ) |
| return mlog_main_exit_hint; |
| |
| ok = stream_get_exit_status(pthread_self(), 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) |
| { |
| pthread_t tids[STREAM_SIMMAX]; |
| int i, ntids; |
| 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; |
| |
| ntids = stream_find_all(states, N(states), tids, STREAM_SIMMAX); |
| if (ntids > 0) { |
| |
| /* print the state of all the streams */ |
| fprintf(mlog_fp, _("%s: %s Summary:\n"), progname, PROGSTR_CAPS ); |
| |
| for (i = 0; i < ntids; i++) { |
| stream_state_t state; |
| int streamix; |
| int exit_code; |
| rv_t exit_return, exit_hint; |
| /* REFERENCED */ |
| bool_t ok; |
| |
| ok = stream_get_exit_status(tids[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 %s " |
| "%s (%s)\n"), |
| progname, |
| streamix, |
| 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"; |
| else status_str = exit_codestring(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 int |
| 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; |
| } |
| |
| void |
| fold_init( fold_t fold, char *infostr, char c ) |
| { |
| size_t infolen; |
| size_t dashlen; |
| size_t predashlen; |
| size_t postdashlen; |
| char *p; |
| char *endp; |
| ix_t cnt; |
| |
| assert( sizeof( fold_t ) == FOLD_LEN + 1 ); |
| |
| infolen = strlen( infostr ); |
| if ( infolen > FOLD_LEN - 4 ) { |
| infolen = FOLD_LEN - 4; |
| } |
| dashlen = FOLD_LEN - infolen - 3; |
| predashlen = dashlen / 2; |
| postdashlen = dashlen - predashlen; |
| |
| p = &fold[ 0 ]; |
| endp = &fold[ sizeof( fold_t ) - 1 ]; |
| |
| assert( p < endp ); |
| *p++ = ' '; |
| for ( cnt = 0 ; cnt < predashlen && p < endp ; cnt++, p++ ) { |
| *p = c; |
| } |
| assert( p < endp ); |
| *p++ = ' '; |
| assert( p < endp ); |
| assert( p + infolen < endp ); |
| strcpy( p, infostr ); |
| p += infolen; |
| assert( p < endp ); |
| *p++ = ' '; |
| assert( p < endp ); |
| for ( cnt = 0 ; cnt < postdashlen && p < endp ; cnt++, p++ ) { |
| *p = c; |
| } |
| assert( p <= endp ); |
| *p = 0; |
| } |