blob: b5201313106b12a139f8c8d2ba5227e942a4d8fc [file] [log] [blame]
/*
* 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;
}