blob: b34e974701bf1176030cc434fc86dc8f1348494b [file] [log] [blame]
/*
* Copyright (c) 2000-2002 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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <time.h>
#include <limits.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <termios.h>
#include <getopt.h>
#include <stdint.h>
#include <sched.h>
#include <pthread.h>
#include <assert.h>
#include <string.h>
#include <uuid/uuid.h>
#include "config.h"
#include "exit.h"
#include "types.h"
#include "stream.h"
#include "cldmgr.h"
#include "getopt.h"
#include "mlog.h"
#include "qlock.h"
#include "lock.h"
#include "dlog.h"
#include "global.h"
#include "drive.h"
#include "media.h"
#include "content.h"
#include "inventory.h"
#ifdef DUMP
/* main.c - main for dump
*/
#endif /* DUMP */
#ifdef RESTORE
/* main.c - main for restore
*/
#endif /* RESTORE */
/* structure definitions used locally ****************************************/
#ifdef RESTORE
#define VMSZ_PER 4 /* proportion of available vm to use in tree */
#endif /* RESTORE */
#define DLOG_TIMEOUT 60 /* time out operator dialog */
#define STOP_TIMEOUT 600 /* seconds after stop req. before abort */
#define ABORT_TIMEOUT 10 /* seconds after abort req. before abort */
#define MINSTACKSZ 0x02000000
#define MAXSTACKSZ 0x08000000
/* declarations of externally defined global symbols *************************/
extern void rmt_turnonmsgs(int);
/* forward declarations of locally defined global functions ******************/
void usage( void );
bool_t preemptchk( int );
/* forward declarations of locally defined static functions ******************/
static bool_t loadoptfile( int *argcp, char ***argvp );
static char * stripquotes( char *p );
static void shiftleftby1( char *p, char *endp );
static void sighandler( int );
static int childmain( void * );
static bool_t sigint_dialog( void );
static char *sigintstr( void );
#ifdef DUMP
static bool_t set_rlimits( void );
#endif /* DUMP */
#ifdef RESTORE
static bool_t set_rlimits( size64_t * );
#endif /* RESTORE */
static char *sig_numstring( int num );
static char *strpbrkquotes( char *p, const char *sep );
/* definition of locally defined global variables ****************************/
char *progname = 0; /* used in all error output */
char *homedir = 0; /* directory invoked from */
bool_t pipeline = BOOL_FALSE;
bool_t stdoutpiped = BOOL_FALSE;
pthread_t parenttid;
char *sistr;
size_t pgsz;
size_t pgmask;
/* definition of locally defined static variables *****************************/
static rlim64_t minstacksz;
static rlim64_t maxstacksz;
#ifdef RESTORE
static size64_t vmsz;
#endif /* RESTORE */
static time32_t stop_deadline;
static bool_t stop_in_progress;
static bool_t sighup_received;
static bool_t sigterm_received;
static bool_t sigquit_received;
static bool_t sigint_received;
/* REFERENCED */
static int sigstray_received;
static bool_t progrpt_enabledpr;
static time32_t progrpt_interval;
static time32_t progrpt_deadline;
/* definition of locally defined global functions ****************************/
int
main( int argc, char *argv[] )
{
int c;
#ifdef DUMP
uid_t euid;
#endif /* DUMP */
ix_t stix; /* stream index */
bool_t infoonly;
#ifdef DUMP
global_hdr_t *gwhdrtemplatep;
#endif /* DUMP */
bool_t init_error;
bool_t coredump_requested = BOOL_FALSE;
int exitcode;
rlim64_t tmpstacksz;
struct sigaction sa;
int prbcld_xc = EXIT_NORMAL;
int xc;
bool_t ok;
/* REFERENCED */
int rval;
int err;
/* sanity checks
*/
assert( sizeof( char_t ) == 1 );
assert( sizeof( u_char_t ) == 1 );
assert( sizeof( int32_t ) == 4 );
assert( sizeof( uint32_t ) == 4 );
assert( sizeof( size32_t ) == 4 );
assert( sizeof( int64_t ) == 8 );
assert( sizeof( uint64_t ) == 8 );
assert( sizeof( size64_t ) == 8 );
/* record the command name used to invoke
*/
progname = argv[ 0 ];
/* setup I18N support */
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
/* bootstrap message logging (stage 0)
*/
mlog_init0();
/* Get the parent's pthread id. will be used
* to differentiate parent from children.
*/
parenttid = pthread_self( );
rval = atexit(mlog_exit_flush);
assert(rval == 0);
/* pre-scan the command line for the option file option.
* if found, create a new argv.
*/
ok = loadoptfile( &argc, &argv );
if ( ! ok ) {
return mlog_exit(EXIT_ERROR, RV_OPT);
}
/* initialize message logging (stage 1)
*/
ok = mlog_init1( argc, argv );
if ( ! ok ) {
return mlog_exit(EXIT_ERROR, RV_INIT);
}
/* scan the command line for the info, progress
* report options, and stacksz.
*/
minstacksz = MINSTACKSZ;
maxstacksz = MAXSTACKSZ;
infoonly = BOOL_FALSE;
progrpt_enabledpr = BOOL_FALSE;
optind = 1;
opterr = 0;
while ( ( c = getopt( argc, argv, GETOPT_CMDSTRING )) != EOF ) {
switch ( c ) {
case GETOPT_MINSTACKSZ:
if ( ! optarg || optarg[ 0 ] == '-' ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_NOLOCK,
_("-%c argument missing\n"),
c );
usage( );
return mlog_exit(EXIT_ERROR, RV_OPT);
}
errno = 0;
tmpstacksz = strtoull( optarg, 0, 0 );
if ( tmpstacksz == UINT64_MAX
||
errno == ERANGE ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_NOLOCK,
_("-%c argument (%s) invalid\n"),
c,
optarg );
usage( );
return mlog_exit(EXIT_ERROR, RV_OPT);
}
minstacksz = tmpstacksz;
break;
case GETOPT_MAXSTACKSZ:
if ( ! optarg || optarg[ 0 ] == '-' ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_NOLOCK,
_("-%c argument missing\n"),
c );
usage( );
return mlog_exit(EXIT_ERROR, RV_OPT);
}
errno = 0;
tmpstacksz = strtoull( optarg, 0, 0 );
if ( tmpstacksz == UINT64_MAX
||
errno == ERANGE ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_NOLOCK,
_("-%c argument (%s) invalid\n"),
c,
optarg );
usage( );
return mlog_exit(EXIT_ERROR, RV_OPT);
}
maxstacksz = tmpstacksz;
break;
case GETOPT_HELP:
infoonly = BOOL_TRUE;
mlog_exit_hint(RV_USAGE);
break;
case GETOPT_PROGRESS:
if ( ! optarg || optarg[ 0 ] == '-' ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_NOLOCK,
_("-%c argument missing\n"),
c );
usage( );
return mlog_exit(EXIT_ERROR, RV_OPT);
}
progrpt_interval = ( time32_t )atoi( optarg );
if ( progrpt_interval > 0 ) {
progrpt_enabledpr = BOOL_TRUE;
} else {
progrpt_enabledpr = BOOL_FALSE;
}
break;
}
}
/* sanity check resultant stack size limits
*/
if ( minstacksz > maxstacksz ) {
mlog( MLOG_NORMAL
|
MLOG_ERROR
|
MLOG_NOLOCK
|
MLOG_PROC,
_("specified minimum stack size is larger than maximum: "
"min is 0x%llx, max is 0x%llx\n"),
minstacksz,
maxstacksz );
return mlog_exit(EXIT_ERROR, RV_INIT);
}
if ( argc == 1 ) {
infoonly = BOOL_TRUE;
}
/* set a progress report deadline to allow preemptchk() to
* report
*/
if ( progrpt_enabledpr ) {
progrpt_deadline = time( 0 ) + progrpt_interval;
}
/* intitialize the stream manager
*/
stream_init( );
#ifdef DUMP
/* set the memory limits to their appropriate values.
*/
ok = set_rlimits( );
#endif /* DUMP */
#ifdef RESTORE
/* set the memory limits to their appropriate values. this is necessary
* to accomodate the tree abstraction and some recursive functions.
* also determines maximum vm, which will be budgeted among the
* various abstractions.
*/
ok = set_rlimits( &vmsz );
#endif /* RESTORE */
if ( ! ok ) {
return mlog_exit(EXIT_ERROR, RV_INIT);
}
/* initialize message logging (stage 2) - allocate the message lock
*/
ok = mlog_init2( );
if ( ! ok ) {
return mlog_exit(EXIT_ERROR, RV_INIT);
}
/* initialize the critical region lock
*/
lock_init( );
rmt_turnonmsgs(1); /* turn on WARNING msgs for librmt */
mlog( MLOG_NITTY + 1, "INTGENMAX == %ld (0x%lx)\n", INTGENMAX, INTGENMAX );
mlog( MLOG_NITTY + 1, "UINTGENMAX == %lu (0x%lx)\n", UINTGENMAX, UINTGENMAX );
mlog( MLOG_NITTY + 1, "OFF64MAX == %lld (0x%llx)\n", OFF64MAX, OFF64MAX );
mlog( MLOG_NITTY + 1, "OFFMAX == %ld (0x%lx)\n", OFFMAX, OFFMAX );
mlog( MLOG_NITTY + 1, "SIZEMAX == %lu (0x%lx)\n", SIZEMAX, SIZEMAX );
mlog( MLOG_NITTY + 1, "INOMAX == %lu (0x%lx)\n", INOMAX, INOMAX );
mlog( MLOG_NITTY + 1, "TIMEMAX == %ld (0x%lx)\n", TIMEMAX, TIMEMAX );
mlog( MLOG_NITTY + 1, "SIZE64MAX == %llu (0x%llx)\n", SIZE64MAX, SIZE64MAX );
mlog( MLOG_NITTY + 1, "INO64MAX == %llu (0x%llx)\n", INO64MAX, INO64MAX );
mlog( MLOG_NITTY + 1, "UINT64MAX == %llu (0x%llx)\n", UINT64MAX, UINT64MAX );
mlog( MLOG_NITTY + 1, "INT64MAX == %lld (0x%llx)\n", INT64MAX, INT64MAX );
mlog( MLOG_NITTY + 1, "UINT32MAX == %u (0x%x)\n", UINT32MAX, UINT32MAX );
mlog( MLOG_NITTY + 1, "INT32MAX == %d (0x%x)\n", INT32MAX, INT32MAX );
mlog( MLOG_NITTY + 1, "INT16MAX == %d (0x%x)\n", INT16MAX, INT16MAX );
mlog( MLOG_NITTY + 1, "UINT16MAX == %u (0x%x)\n", UINT16MAX, UINT16MAX );
/* ask the system for the true vm page size, which must be used
* in all mmap calls
*/
pgsz = ( size_t )getpagesize( );
mlog( MLOG_DEBUG | MLOG_PROC,
"getpagesize( ) returns %u\n",
pgsz );
assert( ( int )pgsz > 0 );
pgmask = pgsz - 1;
/* report parent tid
*/
mlog( MLOG_DEBUG | MLOG_PROC,
"parent tid is %lu\n",
parenttid );
/* get the current working directory: this is where we will dump
* core, if necessary. some tmp files may be placed here as well.
*/
homedir = getcwd( 0, MAXPATHLEN );
if ( ! homedir ) {
mlog( MLOG_NORMAL | MLOG_ERROR,
_("unable to determine current directory: %s\n"),
strerror( errno ));
return mlog_exit(EXIT_ERROR, RV_INIT);
}
/* sanity check the inventory database directory, setup global paths
*/
ok = inv_setup_base( );
if ( ! ok ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_NOLOCK,
_("both /var/lib/xfsdump and /var/xfsdump exist - fatal\n"));
return mlog_exit(EXIT_ERROR, RV_INIT);
}
/* if just looking for info, oblige
*/
if ( infoonly ) {
mlog( MLOG_NORMAL,
_("version %s (dump format %d.0)\n"),
VERSION, GLOBAL_HDR_VERSION );
usage( );
return mlog_exit(EXIT_NORMAL, RV_OK); /* normal termination */
}
/* if an inventory display is requested, do it and exit
*/
if ( ! inv_DEBUG_print( argc, argv )) {
return mlog_exit(EXIT_NORMAL, RV_OK); /* normal termination */
}
#ifdef DUMP
/* insist that the effective user id is root.
* this must appear after inv_DEBUG_print(),
* so it may be done without root privilege.
*/
euid = geteuid( );
mlog( MLOG_DEBUG | MLOG_PROC,
"effective user id is %d\n",
euid );
if ( euid != 0 ) {
mlog( MLOG_NORMAL,
_("effective user ID must be root\n") );
return mlog_exit(EXIT_ERROR, RV_PERM);
}
#endif /* DUMP */
/* initialize operator dialog capability
*/
ok = dlog_init( argc, argv );
if ( ! ok ) {
return mlog_exit(EXIT_ERROR, RV_INIT);
}
/* initialize the child process manager
*/
ok = cldmgr_init( );
if ( ! ok ) {
return mlog_exit(EXIT_ERROR, RV_INIT);
}
/* select and instantiate a drive manager for each stream. this
* is the first pass at initialization, so don't do anything
* terribly time-consuming here. A second initialization pass
* will be done shortly.
*/
ok = drive_init1( argc, argv );
if ( ! ok ) {
return mlog_exit(EXIT_ERROR, RV_INIT);
}
/* check the drives to see if we're in a pipeline.
* if not, check stdout anyway, in case someone is trying to pipe
* the log messages into more, tee, ...
*/
if ( drivepp[ 0 ]->d_isunnamedpipepr ) {
mlog( MLOG_DEBUG | MLOG_NOTE,
"pipeline detected\n" );
pipeline = BOOL_TRUE;
} else {
struct stat64 statbuf;
if ( fstat64( 1, &statbuf ) == 0
&&
( statbuf.st_mode & S_IFMT ) == S_IFIFO ) {
stdoutpiped = BOOL_TRUE;
}
}
/* announce version and instructions
*/
sistr = sigintstr( );
mlog( MLOG_VERBOSE,
_("version %s (dump format %d.0)"),
VERSION, GLOBAL_HDR_VERSION );
if ( ! pipeline && ! stdoutpiped && sistr && dlog_allowed( )) {
mlog( MLOG_VERBOSE | MLOG_BARE, _(
" - "
"type %s for status and control\n"),
sistr );
} else {
mlog( MLOG_VERBOSE | MLOG_BARE,
"\n" );
}
#ifdef DUMP
/* build a global write header template
*/
gwhdrtemplatep = global_hdr_alloc( argc, argv );
if ( ! gwhdrtemplatep ) {
return mlog_exit(EXIT_ERROR, RV_INIT);
}
#endif /* DUMP */
/* tell mlog how many streams there are. the format of log messages
* depends on whether there are one or many.
*/
mlog_tell_streamcnt( drivecnt );
/* initialize the state of signal processing. if in a pipeline, just
* want to exit when a signal is received. otherwise, hold signals so
* they don't interfere with sys calls; they will be released at
* pre-emption points and upon pausing in the main loop.
*
* note that since we're multi-threaded, handling SIGCHLD causes
* problems with system()'s ability to obtain a child's exit status
* (because the main thread may process SIGCHLD before the thread
* running system() calls waitpid()). likewise explicitly ignoring
* SIGCHLD also prevents system() from getting an exit status.
* therefore we don't do anything with SIGCHLD.
*/
sigfillset(&sa.sa_mask);
sa.sa_flags = 0;
/* always ignore SIGPIPE, instead handle EPIPE as part
* of normal sys call error handling.
*/
sa.sa_handler = SIG_IGN;
sigaction( SIGPIPE, &sa, NULL );
if ( ! pipeline ) {
sigset_t blocked_set;
stop_in_progress = BOOL_FALSE;
coredump_requested = BOOL_FALSE;
sighup_received = BOOL_FALSE;
sigterm_received = BOOL_FALSE;
sigint_received = BOOL_FALSE;
sigquit_received = BOOL_FALSE;
sigstray_received = BOOL_FALSE;
alarm( 0 );
sigemptyset( &blocked_set );
sigaddset( &blocked_set, SIGINT );
sigaddset( &blocked_set, SIGHUP );
sigaddset( &blocked_set, SIGTERM );
sigaddset( &blocked_set, SIGQUIT );
sigaddset( &blocked_set, SIGALRM );
sigaddset( &blocked_set, SIGUSR1 );
pthread_sigmask( SIG_SETMASK, &blocked_set, NULL );
sa.sa_handler = sighandler;
sigaction( SIGINT, &sa, NULL );
sigaction( SIGHUP, &sa, NULL );
sigaction( SIGTERM, &sa, NULL );
sigaction( SIGQUIT, &sa, NULL );
sigaction( SIGALRM, &sa, NULL );
sigaction( SIGUSR1, &sa, NULL );
}
/* do content initialization.
*/
#ifdef DUMP
ok = content_init( argc, argv, gwhdrtemplatep );
#endif /* DUMP */
#ifdef RESTORE
ok = content_init( argc, argv, vmsz / VMSZ_PER );
#endif /* RESTORE */
if ( ! ok ) {
err = mlog_exit(EXIT_ERROR, RV_INIT);
goto err_free;
}
/* if in a pipeline, go single-threaded with just one stream.
*/
if ( pipeline ) {
int exitcode;
sa.sa_handler = sighandler;
sigaction( SIGINT, &sa, NULL );
sigaction( SIGHUP, &sa, NULL );
sigaction( SIGTERM, &sa, NULL );
sigaction( SIGQUIT, &sa, NULL );
ok = drive_init2( argc,
argv,
#ifdef DUMP
gwhdrtemplatep );
#endif /* DUMP */
#ifdef RESTORE
( global_hdr_t * )0 );
#endif /* RESTORE */
if ( ! ok ) {
err = mlog_exit(EXIT_ERROR, RV_INIT);
goto err_free;
}
ok = drive_init3( );
if ( ! ok ) {
err = mlog_exit(EXIT_ERROR, RV_INIT);
goto err_free;
}
#ifdef DUMP
exitcode = content_stream_dump( 0 );
#endif /* DUMP */
#ifdef RESTORE
exitcode = content_stream_restore( 0 );
#endif /* RESTORE */
if ( exitcode != EXIT_NORMAL ) {
( void )content_complete( );
/* for cleanup side-effect */
err = mlog_exit(exitcode, RV_UNKNOWN);
} else if ( content_complete( )) {
err = mlog_exit(EXIT_NORMAL, RV_OK);
} else {
err = mlog_exit(EXIT_INTERRUPT, RV_UNKNOWN);
}
goto err_free;
}
/* used to skip to end if errors occur during any
* stage of initialization.
*/
init_error = BOOL_FALSE;
/* now do the second and third passes of drive initialization.
* allocate per-stream write and read headers. if a drive
* manager uses a slave process, it should be created now,
* using cldmgr_create( ). each drive manager may use the slave to
* asynchronously read the media file header, typically a very
* time-consuming chore. drive_init3 will synchronize with each slave.
*/
if ( ! init_error ) {
ok = drive_init2( argc,
argv,
#ifdef DUMP
gwhdrtemplatep );
#endif /* DUMP */
#ifdef RESTORE
( global_hdr_t * )0 );
#endif /* RESTORE */
if ( ! ok ) {
init_error = BOOL_TRUE;
}
}
if ( ! init_error ) {
ok = drive_init3( );
if ( ! ok ) {
init_error = BOOL_TRUE;
}
}
/* create a child thread for each stream. drivecnt global from
* drive.h, initialized by drive_init[12]
*/
if ( ! init_error ) {
for ( stix = 0 ; stix < drivecnt ; stix++ ) {
ok = cldmgr_create( childmain,
stix,
"child",
( void * )stix );
if ( ! ok ) {
init_error = BOOL_TRUE;
}
}
}
/* loop here, waiting for children to die, processing operator
* signals.
*/
if ( progrpt_enabledpr ) {
( void )alarm( ( uint )progrpt_interval );
}
for ( ; ; ) {
time32_t now;
bool_t stop_requested = BOOL_FALSE;
int stop_timeout = -1;
sigset_t empty_set;
/* if there was an initialization error,
* immediately stop all children.
*/
if ( init_error ) {
stop_timeout = STOP_TIMEOUT;
stop_requested = BOOL_TRUE;
}
/* if one or more children died abnormally, request a
* stop. furthermore, note that core should be dumped if
* the child explicitly exited with EXIT_FAULT.
*/
xc = cldmgr_join( );
if ( xc ) {
if ( xc == EXIT_FAULT ) {
coredump_requested = BOOL_TRUE;
stop_timeout = ABORT_TIMEOUT;
} else {
stop_timeout = STOP_TIMEOUT;
}
prbcld_xc = xc;
stop_requested = BOOL_TRUE;
}
/* all children died normally. break out.
*/
if ( cldmgr_remainingcnt( ) == 0 ) {
mlog( MLOG_DEBUG,
"all children have exited\n" );
break;
}
/* get the current time
*/
now = time( 0 );
/* check for stop timeout. request a core dump and bail
*/
if ( stop_in_progress && now >= stop_deadline ) {
mlog( MLOG_NORMAL | MLOG_ERROR,
_("session interrupt timeout\n") );
coredump_requested = BOOL_TRUE;
break;
}
/* operator sent SIGINT. if dialog allowed, enter dialog.
* otherwise treat as a hangup and request a stop.
*/
if ( sigint_received ) {
mlog( MLOG_DEBUG | MLOG_PROC,
"SIGINT received\n" );
if ( stop_in_progress ) {
if ( dlog_allowed( )) {
( void )sigint_dialog( );
}
/*
mlog( MLOG_NORMAL,
_("session interrupt in progress: "
"please wait\n") );
*/
} else {
if ( dlog_allowed( )) {
stop_requested = sigint_dialog( );
} else {
stop_requested = BOOL_TRUE;
}
stop_timeout = STOP_TIMEOUT;
}
/* important that this appear after dialog.
* allows dialog to be terminated with SIGINT,
* without infinite loop.
*/
sigint_received = BOOL_FALSE;
}
/* refresh the current time in case in dialog for a while
*/
now = time( 0 );
/* request a stop on hangup
*/
if ( sighup_received ) {
mlog( MLOG_DEBUG | MLOG_PROC,
"SIGHUP received\n" );
stop_requested = BOOL_TRUE;
stop_timeout = STOP_TIMEOUT;
sighup_received = BOOL_FALSE;
}
/* request a stop on termination request
*/
if ( sigterm_received ) {
mlog( MLOG_DEBUG | MLOG_PROC,
"SIGTERM received\n" );
stop_requested = BOOL_TRUE;
stop_timeout = STOP_TIMEOUT;
sigterm_received = BOOL_FALSE;
}
/* operator send SIGQUIT. treat like an interrupt,
* but force a core dump
*/
if ( sigquit_received ) {
mlog( MLOG_NORMAL | MLOG_PROC,
"SIGQUIT received\n" );
if ( stop_in_progress ) {
mlog( MLOG_NORMAL,
_("session interrupt in progress: "
"please wait\n") );
stop_deadline = now;
} else {
stop_requested = BOOL_TRUE;
stop_timeout = ABORT_TIMEOUT;
sigquit_received = BOOL_FALSE;
coredump_requested = BOOL_TRUE;
}
}
/* see if need to initiate a stop
*/
if ( stop_requested && ! stop_in_progress ) {
mlog( MLOG_NORMAL,
_("initiating session interrupt (timeout in %d sec)\n"),
stop_timeout);
mlog_exit_hint(RV_INTR);
stop_in_progress = BOOL_TRUE;
cldmgr_stop( );
assert( stop_timeout >= 0 );
stop_deadline = now + ( time32_t )stop_timeout;
}
/* set alarm if needed (note time stands still during dialog)
*/
if ( stop_in_progress ) {
int timeout = ( int )( stop_deadline - now );
if ( timeout < 0 ) {
timeout = 0;
}
mlog( MLOG_DEBUG | MLOG_PROC,
"setting alarm for %d second%s\n",
timeout,
timeout == 1 ? "" : "s" );
( void )alarm( ( uint )timeout );
if ( timeout == 0 ) {
continue;
}
}
if ( progrpt_enabledpr && ! stop_in_progress ) {
bool_t need_progrptpr = BOOL_FALSE;
while ( now >= progrpt_deadline ) {
need_progrptpr = BOOL_TRUE;
progrpt_deadline += progrpt_interval;
}
if ( need_progrptpr ) {
size_t statlinecnt;
char **statline;
ix_t i;
statlinecnt = content_statline( &statline );
for ( i = 0 ; i < statlinecnt ; i++ ) {
mlog( MLOG_NORMAL,
statline[ i ] );
}
}
( void )alarm( ( uint )( progrpt_deadline
-
now ));
}
/* sleep until next signal
*/
sigemptyset( &empty_set );
sigsuspend( &empty_set );
( void )alarm( 0 );
}
/* check if core dump requested
*/
if ( coredump_requested ) {
mlog( MLOG_DEBUG | MLOG_PROC,
"core dump requested, aborting (pid %d)\n",
getpid() );
abort();
}
/* determine if dump or restore was interrupted
* or an initialization error occurred.
*/
if ( init_error ) {
( void )content_complete( );
exitcode = EXIT_ERROR;
} else {
if ( content_complete( ) ) {
if (prbcld_xc != EXIT_NORMAL)
exitcode = EXIT_ERROR;
else
exitcode = EXIT_NORMAL;
} else {
exitcode = EXIT_INTERRUPT;
if ( mlog_get_hint() == RV_NONE )
mlog_exit_hint(RV_INCOMPLETE);
}
}
err = mlog_exit(exitcode, RV_UNKNOWN);
err_free:
#ifdef DUMP
global_hdr_free( gwhdrtemplatep );
#endif /* DUMP */
return err;
}
#define ULO( f, o ) fprintf( stderr, \
"%*s[ -%c %s ]\n", \
ps, \
ns, \
o, \
f ), \
ps = pfxsz
#define ULN( f ) fprintf( stderr, \
"%*s[ %s ]\n", \
ps, \
ns, \
f ), \
ps = pfxsz
void
usage( void )
{
int pfxsz;
int ps = 0;
char *ns = "";
pfxsz = fprintf(stderr, _("%s: usage: %s "),
progname, basename(progname));
#ifdef DUMP
ULO(_("(dump DMF dualstate files as offline)"), GETOPT_DUMPASOFFLINE );
ULO(_("<blocksize>"), GETOPT_BLOCKSIZE );
ULO(_("<media change alert program> "), GETOPT_ALERTPROG );
ULO(_("<dump media file size> "), GETOPT_FILESZ );
ULO(_("(allow files to be excluded)"), GETOPT_EXCLUDEFILES );
ULO(_("<destination> ..."), GETOPT_DUMPDEST );
ULO(_("(help)"), GETOPT_HELP );
ULO(_("<level>"), GETOPT_LEVEL );
ULO(_("(force usage of minimal rmt)"), GETOPT_MINRMT );
ULO(_("(overwrite tape)"), GETOPT_OVERWRITE );
ULO(_("<seconds between progress reports>"), GETOPT_PROGRESS );
ULO(_("<use QIC tape settings>"), GETOPT_QIC );
ULO(_("<subtree> ..."), GETOPT_SUBTREE );
ULO(_("<file> (use file mtime for dump time"), GETOPT_DUMPTIME );
ULO(_("<verbosity {silent, verbose, trace}>"), GETOPT_VERBOSITY );
ULO(_("<maximum file size>"), GETOPT_MAXDUMPFILESIZE );
ULO(_("(don't dump extended file attributes)"), GETOPT_NOEXTATTR );
ULO(_("<base dump session id>"), GETOPT_BASED );
#ifdef REVEAL
ULO(_("(generate tape record checksums)"), GETOPT_RECCHKSUM );
#endif /* REVEAL */
ULO(_("(skip unchanged directories)"), GETOPT_NOUNCHANGEDDIRS );
ULO(_("(pre-erase media)"), GETOPT_ERASE );
ULO(_("(don't prompt)"), GETOPT_FORCE );
#ifdef REVEAL
ULO(_("<minimum thread stack size>"), GETOPT_MINSTACKSZ );
ULO(_("<maximum thread stack size>"), GETOPT_MAXSTACKSZ );
#endif /* REVEAL */
ULO(_("(display dump inventory)"), GETOPT_INVPRINT );
ULO(_("(inhibit inventory update)"), GETOPT_NOINVUPDATE );
ULO(_("(generate format 2 dump)"), GETOPT_FMT2COMPAT );
ULO(_("<session label>"), GETOPT_DUMPLABEL );
ULO(_("<media label> ..."), GETOPT_MEDIALABEL );
#ifdef REVEAL
ULO(_("(timestamp messages)"), GETOPT_TIMESTAMP );
#endif /* REVEAL */
ULO(_("<options file>"), GETOPT_OPTFILE );
#ifdef REVEAL
ULO(_("(pin down I/O buffers)"), GETOPT_RINGPIN );
#endif /* REVEAL */
ULO(_("(resume)"), GETOPT_RESUME );
ULO(_("(don't timeout dialogs)"), GETOPT_NOTIMEOUTS );
#ifdef REVEAL
ULO(_("(unload media when change needed)"), GETOPT_UNLOAD );
ULO(_("(show subsystem in messages)"), GETOPT_SHOWLOGSS );
ULO(_("(show verbosity in messages)"), GETOPT_SHOWLOGLEVEL );
#endif /* REVEAL */
ULO(_("<I/O buffer ring length>"), GETOPT_RINGLEN );
ULN(_("- (stdout)") );
ULN(_("<source (mntpnt|device)>") );
#endif /* DUMP */
#ifdef RESTORE
ULO(_("<alt. workspace dir> ..."), GETOPT_WORKSPACE );
ULO(_("<blocksize>"), GETOPT_BLOCKSIZE );
ULO(_("<media change alert program> "), GETOPT_ALERTPROG );
ULO(_("(don't overwrite existing files)"), GETOPT_EXISTING );
ULO(_("<source> ..."), GETOPT_DUMPDEST );
ULO(_("(help)"), GETOPT_HELP );
ULO(_("(interactive)"), GETOPT_INTERACTIVE );
ULO(_("(force usage of minimal rmt)"), GETOPT_MINRMT );
ULO(_("<file> (restore only if newer than)"), GETOPT_NEWER );
ULO(_("(restore owner/group even if not root)"),GETOPT_OWNER );
ULO(_("<seconds between progress reports>"), GETOPT_PROGRESS );
ULO(_("<use QIC tape settings>"), GETOPT_QIC );
ULO(_("(cumulative restore)"), GETOPT_CUMULATIVE );
ULO(_("<subtree> ..."), GETOPT_SUBTREE );
ULO(_("(contents only)"), GETOPT_TOC );
ULO(_("<verbosity {silent, verbose, trace}>"), GETOPT_VERBOSITY );
ULO(_("(use small tree window)"), GETOPT_SMALLWINDOW );
ULO(_("(don't restore extended file attributes)"),GETOPT_NOEXTATTR );
ULO(_("(restore root dir owner/permissions)"), GETOPT_ROOTPERM );
ULO(_("(restore DMAPI event settings)"), GETOPT_SETDM );
#ifdef REVEAL
ULO(_("(check tape record checksums)"), GETOPT_RECCHKSUM );
#endif /* REVEAL */
ULO(_("(don't overwrite if changed)"), GETOPT_CHANGED );
ULO(_("(don't prompt)"), GETOPT_FORCE );
ULO(_("(display dump inventory)"), GETOPT_INVPRINT );
ULO(_("(inhibit inventory update)"), GETOPT_NOINVUPDATE );
ULO(_("(force use of format 2 generation numbers)"),GETOPT_FMT2COMPAT );
ULO(_("<session label>"), GETOPT_DUMPLABEL );
#ifdef REVEAL
ULO(_("(timestamp messages)"), GETOPT_TIMESTAMP );
#endif /* REVEAL */
ULO(_("<options file>"), GETOPT_OPTFILE );
#ifdef REVEAL
ULO(_("(pin down I/O buffers)"), GETOPT_RINGPIN );
#endif /* REVEAL */
ULO(_("(force interrupted session completion)"),GETOPT_SESSCPLT );
ULO(_("(resume)"), GETOPT_RESUME );
ULO(_("<session id>"), GETOPT_SESSIONID );
ULO(_("(don't timeout dialogs)"), GETOPT_NOTIMEOUTS );
#ifdef REVEAL
ULO(_("(unload media when change needed)"), GETOPT_UNLOAD );
ULO(_("(show subsystem in messages)"), GETOPT_SHOWLOGSS );
ULO(_("(show verbosity in messages)"), GETOPT_SHOWLOGLEVEL );
#endif /* REVEAL */
ULO(_("<excluded subtree> ..."), GETOPT_NOSUBTREE );
ULO(_("<I/O buffer ring length>"), GETOPT_RINGLEN );
ULN(_("- (stdin)") );
ULN(_("<destination>") );
#endif /* RESTORE */
/* anywhere usage is called we will exit shortly after...
* catch all of those cases below
*/
(void) mlog_exit(EXIT_ERROR, RV_OPT);
}
/* returns TRUE if preemption
*/
bool_t
preemptchk( int flg )
{
bool_t preempt_requested;
int i;
int sigs[] = { SIGINT, SIGHUP, SIGTERM, SIGQUIT };
int num_sigs = sizeof(sigs) / sizeof(sigs[0]);
sigset_t pending_set, handle_set;
/* see if a progress report needed
*/
if ( progrpt_enabledpr ) {
time32_t now = time( 0 );
bool_t need_progrptpr = BOOL_FALSE;
while ( now >= progrpt_deadline ) {
need_progrptpr = BOOL_TRUE;
progrpt_deadline += progrpt_interval;
}
if ( need_progrptpr ) {
size_t statlinecnt;
char **statline;
ix_t i;
statlinecnt = content_statline( &statline );
for ( i = 0 ; i < statlinecnt ; i++ ) {
mlog( MLOG_NORMAL,
statline[ i ] );
}
}
}
/* Progress report only */
if (flg == PREEMPT_PROGRESSONLY) {
return BOOL_FALSE;
}
/* signals not caught if in a pipeline
*/
if ( pipeline ) {
return BOOL_FALSE;
}
/* release signals momentarily to let any pending ones
* invoke signal handler and set flags
*/
sigpending( &pending_set );
for ( i = 0; i < num_sigs; i++ ) {
if ( sigismember( &pending_set, sigs[i] ) == 1 ) {
sigfillset( &handle_set );
sigdelset( &handle_set, sigs[i] );
sigsuspend( &handle_set );
}
}
preempt_requested = BOOL_FALSE;
if ( sigint_received ) {
mlog( MLOG_DEBUG | MLOG_PROC,
"SIGINT received (preempt)\n" );
if ( dlog_allowed( )) {
preempt_requested = sigint_dialog( );
} else {
preempt_requested = BOOL_TRUE;
}
/* important that this appear after dialog.
* allows dialog to be terminated with SIGINT,
* without infinite loop.
*/
sigint_received = BOOL_FALSE;
}
if ( sighup_received ) {
mlog( MLOG_DEBUG | MLOG_PROC,
"SIGHUP received (prempt)\n" );
preempt_requested = BOOL_TRUE;
sighup_received = BOOL_FALSE;
}
if ( sigterm_received ) {
mlog( MLOG_DEBUG | MLOG_PROC,
"SIGTERM received (prempt)\n" );
preempt_requested = BOOL_TRUE;
sigterm_received = BOOL_FALSE;
}
if ( sigquit_received ) {
mlog( MLOG_DEBUG | MLOG_PROC,
"SIGQUIT received (preempt)\n" );
preempt_requested = BOOL_TRUE;
sigquit_received = BOOL_FALSE;
}
return preempt_requested;
}
/* definition of locally defined static functions ****************************/
static bool_t
loadoptfile( int *argcp, char ***argvp )
{
char *optfilename;
ix_t optfileix = 0;
int fd;
size_t sz;
int i;
struct stat64 stat;
char *argbuf;
char *p;
size_t tokencnt;
int nread;
const char *sep = " \t\n\r";
char **newargv;
int c;
int rval;
/* see if option specified
*/
optind = 1;
opterr = 0;
optfilename = 0;
while ( ( c = getopt( *argcp, *argvp, GETOPT_CMDSTRING )) != EOF ) {
switch ( c ) {
case GETOPT_OPTFILE:
if ( ! optarg || optarg[ 0 ] == '-' ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_NOLOCK,
_("-%c argument missing\n"),
c );
usage( );
return BOOL_FALSE;
}
if ( optfilename ) {
mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_NOLOCK,
_("-%c allowed only once\n"),
c );
usage( );
return BOOL_FALSE;
}
optfilename = optarg;
assert( optind > 2 );
optfileix = ( ix_t )optind - 2;
break;
}
}
if ( ! optfilename ) {
return BOOL_TRUE;
}
/* attempt to open the option file
*/
errno = 0;
fd = open( optfilename, O_RDONLY );
if ( fd < 0 ) {
mlog( MLOG_ERROR | MLOG_NOLOCK,
_("cannot open option file %s: %s (%d)\n"),
optfilename,
strerror( errno ),
errno );
return BOOL_FALSE;
}
/* get file status
*/
rval = fstat64( fd, &stat );
if ( rval ) {
mlog( MLOG_ERROR | MLOG_NOLOCK,
_("cannot stat option file %s: %s (%d)\n"),
optfilename,
strerror( errno ),
errno );
close( fd );
return BOOL_FALSE;
}
/* ensure the file is ordinary
*/
if ( ( stat.st_mode & S_IFMT ) != S_IFREG ) {
mlog( MLOG_ERROR | MLOG_NOLOCK,
_("given option file %s is not ordinary file\n"),
optfilename );
close( fd );
return BOOL_FALSE;
}
/* calculate the space required for the cmd line options.
* skip the GETOPT_OPTFILE option which put us here!
*/
sz = 0;
for ( i = 0 ; i < *argcp ; i++ ) {
if ( i == ( int )optfileix ) {
i++; /* to skip option argument */
continue;
}
sz += strlen( ( * argvp )[ i ] ) + 1;
}
/* add in the size of the option file (plus one byte in case
* option file ends without newline, and one NULL for safety)
*/
sz += ( size_t )stat.st_size + 2;
/* allocate an argument buffer
*/
argbuf = ( char * )malloc( sz );
assert( argbuf );
/* copy arg0 (the executable's name ) in first
*/
p = argbuf;
i = 0;
sprintf( p, "%s ", ( * argvp )[ i ] );
p += strlen( ( * argvp )[ i ] ) + 1;
i++;
/* copy the options file into the buffer after the given args
*/
nread = read( fd, ( void * )p, ( size_t )stat.st_size );
if ( nread < 0 ) {
mlog( MLOG_ERROR | MLOG_NOLOCK,
_("read of option file %s failed: %s (%d)\n"),
optfilename,
strerror( errno ),
errno );
close( fd );
return BOOL_FALSE;
}
assert( ( off64_t )nread == stat.st_size );
p += ( size_t )stat.st_size;
*p++ = ' ';
/* copy the remaining command line args into the buffer
*/
for ( ; i < *argcp ; i++ ) {
if ( i == ( int )optfileix ) {
i++; /* to skip option argument */
continue;
}
sprintf( p, "%s ", ( * argvp )[ i ] );
p += strlen( ( * argvp )[ i ] ) + 1;
}
/* null-terminate the entire buffer
*/
*p++ = 0;
assert( ( size_t )( p - argbuf ) <= sz );
/* change newlines and carriage returns into spaces
*/
for ( p = argbuf ; *p ; p++ ) {
if ( strchr( "\n\r", ( int )( *p ))) {
*p = ' ';
}
}
/* count the tokens in the buffer
*/
tokencnt = 0;
p = argbuf;
for ( ; ; ) {
/* start at the first non-separator character
*/
while ( *p && strchr( sep, ( int )( *p ))) {
p++;
}
/* done when NULL encountered
*/
if ( ! *p ) {
break;
}
/* we have a token
*/
tokencnt++;
/* find the end of the first token
*/
p = strpbrkquotes( p, sep );
/* if no more separators, all tokens seen
*/
if ( ! p ) {
break;
}
}
/* if no arguments, can return now
*/
if ( ! tokencnt ) {
close( fd );
return BOOL_TRUE;
}
/* allocate a new argv array to hold the tokens
*/
newargv = ( char ** )calloc( tokencnt, sizeof( char * ));
assert( newargv );
/* null-terminate tokens and place in new argv, after
* extracting quotes and escapes
*/
p = argbuf;
for ( i = 0 ; ; i++ ) {
char *endp = 0;
/* start at the first non-separator character
*/
while ( *p && strchr( sep, ( int )*p )) {
p++;
}
/* done when NULL encountered
*/
if ( ! *p ) {
break;
}
/* better not disagree with counting scan!
*/
assert( i < ( int )tokencnt );
/* find the end of the first token
*/
endp = strpbrkquotes( p, sep );
/* null-terminate if needed
*/
if ( endp ) {
*endp = 0;
}
/* strip quotes and escapes
*/
p = stripquotes( p );
/* stick result in new argv array
*/
newargv[ i ] = p;
/* if no more separators, all tokens seen
*/
if ( ! endp ) {
break;
}
p = endp + 1;
}
/* return new argc anr argv
*/
close( fd );
*argcp = ( int )tokencnt;
*argvp = newargv;
return BOOL_TRUE;
}
/* parent and children share this handler.
*/
static void
sighandler( int signo )
{
/* dialog gets first crack at the signal
*/
if ( dlog_sighandler( signo ) )
return;
/* if in pipeline, don't do anything risky. just quit.
*/
if ( pipeline ) {
int rval;
mlog( MLOG_TRACE | MLOG_NOTE | MLOG_NOLOCK | MLOG_PROC,
_("received signal %d (%s): cleanup and exit\n"),
signo,
sig_numstring( signo ));
if ( content_complete( )) {
rval = EXIT_NORMAL;
} else {
rval = EXIT_INTERRUPT;
}
mlog_exit(rval, RV_NONE);
exit( rval );
}
switch ( signo ) {
case SIGHUP:
/* immediately disable further dialogs
*/
dlog_desist( );
sighup_received = BOOL_TRUE;
break;
case SIGTERM:
/* immediately disable further dialogs
*/
dlog_desist( );
sigterm_received = BOOL_TRUE;
break;
case SIGINT:
sigint_received = BOOL_TRUE;
break;
case SIGQUIT:
/* immediately disable further dialogs
*/
dlog_desist( );
sigquit_received = BOOL_TRUE;
break;
case SIGALRM:
case SIGUSR1:
break;
default:
sigstray_received = signo;
break;
}
}
static int
childmain( void *arg1 )
{
ix_t stix;
int exitcode;
drive_t *drivep;
/* Determine which stream I am.
*/
stix = ( ix_t )arg1;
/* tell the content manager to begin.
*/
#ifdef DUMP
exitcode = content_stream_dump( stix );
#endif /* DUMP */
#ifdef RESTORE
exitcode = content_stream_restore( stix );
#endif /* RESTORE */
/* let the drive manager shut down its slave thread
*/
drivep = drivepp[ stix ];
( * drivep->d_opsp->do_quit )( drivep );
return exitcode;
}
/* ARGSUSED */
static void
prompt_prog_cb( void *uctxp, dlog_pcbp_t pcb, void *pctxp )
{
/* query: ask for a dump label
*/
( * pcb )( pctxp,
progrpt_enabledpr
?
_("please enter seconds between progress reports, "
"or 0 to disable")
:
_("please enter seconds between progress reports") );
}
/* SIGINTR dialog
*
* side affect is to change verbosity level.
* return code of BOOL_TRUE indicates a stop was requested.
*/
#define PREAMBLEMAX ( 7 + 2 * STREAM_SIMMAX )
#define QUERYMAX 3
#define CHOICEMAX 9
#define ACKMAX 7
#define POSTAMBLEMAX 3
static bool_t
sigint_dialog( void )
{
fold_t fold;
char **statline;
ix_t i;
size_t statlinecnt;
char *preamblestr[ PREAMBLEMAX ];
size_t preamblecnt;
char *querystr[ QUERYMAX ];
size_t querycnt;
char *choicestr[ CHOICEMAX ];
size_t choicecnt;
char *ackstr[ ACKMAX ];
size_t ackcnt;
char *postamblestr[ POSTAMBLEMAX ];
size_t postamblecnt;
size_t interruptix;
size_t verbosityix;
size_t metricsix;
size_t controlix;
size_t ioix;
size_t mediachangeix;
#ifdef RESTORE
size_t piix;
size_t roix;
#endif /* RESTORE */
size_t progix;
size_t mllevix;
size_t mlssix;
size_t mltsix;
size_t continueix;
size_t allix;
size_t nochangeix;
size_t responseix;
int ssselected = 0;
bool_t stop_requested = BOOL_FALSE;
/* preamble: the content status line, indicate if interrupt happening
*/
fold_init( fold, _("status and control dialog"), '=' );
statlinecnt = content_statline( &statline );
preamblecnt = 0;
preamblestr[ preamblecnt++ ] = "\n";
preamblestr[ preamblecnt++ ] = fold;
preamblestr[ preamblecnt++ ] = "\n";
preamblestr[ preamblecnt++ ] = "\n";
for ( i = 0 ; i < statlinecnt ; i++ ) {
preamblestr[ preamblecnt++ ] = statline[ i ];
}
if ( stop_in_progress ) {
preamblestr[ preamblecnt++ ] =
_("\nsession interrupt in progress\n");
}
preamblestr[ preamblecnt++ ] = "\n";
assert( preamblecnt <= PREAMBLEMAX );
dlog_begin( preamblestr, preamblecnt );
/* top-level query: a function of session interrupt status
*/
querycnt = 0;
querystr[ querycnt++ ] = _("please select one of "
"the following operations\n");
assert( querycnt <= QUERYMAX );
choicecnt = 0;
if ( ! stop_in_progress ) {
interruptix = choicecnt;
choicestr[ choicecnt++ ] = _("interrupt this session");
} else {
interruptix = SIZEMAX; /* never happen */
}
verbosityix = choicecnt;
choicestr[ choicecnt++ ] = _("change verbosity");
metricsix = choicecnt;
choicestr[ choicecnt++ ] = _("display metrics");
if ( content_media_change_needed ) {
mediachangeix = choicecnt;
choicestr[ choicecnt++ ] = _("confirm media change");
} else {
mediachangeix = SIZEMAX; /* never happen */
}
controlix = choicecnt;
choicestr[ choicecnt++ ] = _("other controls");
continueix = choicecnt;
choicestr[ choicecnt++ ] = _("continue");
assert( choicecnt <= CHOICEMAX );
responseix = dlog_multi_query( querystr,
querycnt,
choicestr,
choicecnt,
0, /* hilitestr */
IXMAX, /* hiliteix */
0, /* defaultstr */
continueix, /* defaultix */
DLOG_TIMEOUT, /* timeout */
continueix, /* timeout ix */
continueix, /* sigint ix */
continueix, /* sighup ix */
continueix ); /* sigquit ix */
if ( responseix == interruptix ) {
ackcnt = 0;
ackstr[ ackcnt++ ] = "\n";
dlog_multi_ack( ackstr,
ackcnt );
querycnt = 0;
querystr[ querycnt++ ] = _("please confirm\n");
assert( querycnt <= QUERYMAX );
choicecnt = 0;
interruptix = choicecnt;
choicestr[ choicecnt++ ] = _("interrupt this session");
nochangeix = choicecnt;
choicestr[ choicecnt++ ] = _("continue");
assert( choicecnt <= CHOICEMAX );
responseix = dlog_multi_query( querystr,
querycnt,
choicestr,
choicecnt,
0, /* hilitestr */
IXMAX, /* hiliteix */
0, /* defaultstr */
nochangeix, /* defaultix */
DLOG_TIMEOUT,/* timeout */
nochangeix, /* timeout ix */
nochangeix, /* sigint ix */
nochangeix, /* sighup ix */
nochangeix);/* sigquit ix */
ackcnt = 0;
if ( responseix == nochangeix ) {
ackstr[ ackcnt++ ] = _("continuing\n");
} else {
ackstr[ ackcnt++ ] = _("interrupt request accepted\n");
stop_requested = BOOL_TRUE;
}
dlog_multi_ack( ackstr,
ackcnt );
} else if ( responseix == verbosityix ) {
ackcnt = 0;
ackstr[ ackcnt++ ] = "\n";
dlog_multi_ack( ackstr,
ackcnt );
querycnt = 0;
querystr[ querycnt++ ] = _("please select one of "
"the following subsystems\n");
assert( querycnt <= QUERYMAX );
choicecnt = 0;
/* number of lines must match number of subsystems
*/
for ( choicecnt = 0 ; choicecnt < MLOG_SS_CNT ; choicecnt++ ) {
choicestr[ choicecnt ] = mlog_ss_names[ choicecnt ];
}
allix = choicecnt;
choicestr[ choicecnt++ ] = _("all of the above");
nochangeix = choicecnt;
choicestr[ choicecnt++ ] = _("no change");
assert( choicecnt <= CHOICEMAX );
responseix = dlog_multi_query( querystr,
querycnt,
choicestr,
choicecnt,
0, /* hilitestr */
IXMAX, /* hiliteix */
0, /* defaultstr */
allix, /* defaultix */
DLOG_TIMEOUT,/* timeout */
nochangeix, /* timeout ix */
nochangeix, /* sigint ix */
nochangeix, /* sighup ix */
nochangeix);/* sigquit ix */
ackcnt = 0;
if ( responseix == nochangeix ) {
ackstr[ ackcnt++ ] = _("no change\n");
} else if ( responseix == allix ) {
ssselected = -1;
ackstr[ ackcnt++ ] = _("all subsystems selected\n\n");
} else {
ssselected = ( int )responseix;
ackstr[ ackcnt++ ] = "\n";
}
dlog_multi_ack( ackstr,
ackcnt );
if ( responseix != nochangeix ) {
querycnt = 0;
querystr[ querycnt++ ] = ("please select one of the "
"following verbosity levels\n");
assert( querycnt <= QUERYMAX );
choicecnt = 0;
choicestr[ choicecnt++ ] = _("silent");
choicestr[ choicecnt++ ] = _("verbose");
choicestr[ choicecnt++ ] = _("trace");
choicestr[ choicecnt++ ] = _("debug");
choicestr[ choicecnt++ ] = _("nitty");
choicestr[ choicecnt++ ] = _("nitty + 1");
nochangeix = choicecnt;
choicestr[ choicecnt++ ] = _("no change");
assert( choicecnt <= CHOICEMAX );
responseix = dlog_multi_query( querystr,
querycnt,
choicestr,
choicecnt,
ssselected == -1
?
0
:
_(" (current)"),/* hilitestr */
ssselected == -1
?
IXMAX
:
( ix_t )mlog_level_ss[ ssselected ], /* hiliteix */
0, /* defaultstr */
nochangeix,/* defaultix */
DLOG_TIMEOUT,/* timeout */
nochangeix, /* timeout ix */
nochangeix, /* sigint ix */
nochangeix, /* sighup ix */
nochangeix);/* sigquit ix */
ackcnt = 0;
if ( responseix == nochangeix
||
( ssselected >= 0
&&
responseix
==
( ix_t )mlog_level_ss[ ssselected ] )) {
ackstr[ ackcnt++ ] = _("no change\n");
} else {
if ( ssselected < 0 ) {
ix_t ssix;
assert( ssselected == -1 );
for ( ssix = 0
;
ssix < MLOG_SS_CNT
;
ssix++ ) {
mlog_level_ss[ ssix ] =
( int )responseix;
}
} else {
mlog_level_ss[ ssselected ] =
( int )responseix;
}
ackstr[ ackcnt++ ] = _("level changed\n");
}
dlog_multi_ack( ackstr,
ackcnt );
}
} else if ( responseix == metricsix ) {
ackcnt = 0;
ackstr[ ackcnt++ ] = "\n";
dlog_multi_ack( ackstr,
ackcnt );
querycnt = 0;
querystr[ querycnt++ ] = _("please select one of "
"the following metrics\n");
assert( querycnt <= QUERYMAX );
choicecnt = 0;
ioix = choicecnt;
choicestr[ choicecnt++ ] = _("I/O");
#ifdef RESTORE
piix = choicecnt;
choicestr[ choicecnt++ ] = _("media inventory status");
roix = choicecnt;
choicestr[ choicecnt++ ] = _("needed media objects");
#endif /* RESTORE */
nochangeix = choicecnt;
choicestr[ choicecnt++ ] = _("continue");
assert( choicecnt <= CHOICEMAX );
responseix = dlog_multi_query( querystr,
querycnt,
choicestr,
choicecnt,
0, /* hilitestr */
IXMAX, /* hiliteix */
0, /* defaultstr */
nochangeix, /* defaultix */
DLOG_TIMEOUT,
nochangeix, /* timeout ix */
nochangeix, /* sigint ix */
nochangeix, /* sighup ix */
nochangeix);/* sigquit ix */
if ( responseix != nochangeix ) {
ackcnt = 0;
ackstr[ ackcnt++ ] = "\n";
dlog_multi_ack( ackstr,
ackcnt );
}
if ( responseix == ioix ) {
drive_display_metrics( );
#ifdef RESTORE
} else if ( responseix == piix ) {
content_showinv( );
} else if ( responseix == roix ) {
content_showremainingobjects( );
#endif /* RESTORE */
}
if ( responseix != nochangeix ) {
querycnt = 0;
querystr[ querycnt++ ] = "\n";
assert( querycnt <= QUERYMAX );
choicecnt = 0;
nochangeix = choicecnt;
choicestr[ choicecnt++ ] = _("continue");
assert( choicecnt <= CHOICEMAX );
responseix = dlog_multi_query( querystr,
querycnt,
choicestr,
choicecnt,
0, /* hilitestr */
IXMAX, /* hiliteix */
0, /* defaultstr */
nochangeix,/* defaultix*/
DLOG_TIMEOUT,
nochangeix,/*timeout ix*/
nochangeix,/* sigint ix*/
nochangeix,/* sighup ix*/
nochangeix);/*sigquitix*/
}
ackcnt = 0;
ackstr[ ackcnt++ ] = _("continuing\n");
dlog_multi_ack( ackstr,
ackcnt );
} else if ( responseix == mediachangeix ) {
ackcnt = 0;
dlog_multi_ack( ackstr,
ackcnt );
ackcnt = 0;
ackstr[ ackcnt++ ] = content_mediachange_query( );
dlog_multi_ack( ackstr,
ackcnt );
} else if ( responseix == controlix ) {
ackcnt = 0;
ackstr[ ackcnt++ ] = "\n";
dlog_multi_ack( ackstr,
ackcnt );
querycnt = 0;
querystr[ querycnt++ ] = _("please select one of "
"the following controls\n");
assert( querycnt <= QUERYMAX );
choicecnt = 0;
progix = choicecnt;
if ( progrpt_enabledpr ) {
choicestr[ choicecnt++ ] = _("change interval of "
"or disable progress reports");
} else {
choicestr[ choicecnt++ ] = _("enable progress reports");
}
mllevix = choicecnt;
if ( mlog_showlevel ) {
choicestr[ choicecnt++ ] = _("hide log message levels");
} else {
choicestr[ choicecnt++ ] = _("show log message levels");
}
mlssix = choicecnt;
if ( mlog_showss ) {
choicestr[ choicecnt++ ] = _("hide log message subsystems");
} else {
choicestr[ choicecnt++ ] = _("show log message subsystems");
}
mltsix = choicecnt;
if ( mlog_timestamp ) {
choicestr[ choicecnt++ ] = _("hide log message timestamps");
} else {
choicestr[ choicecnt++ ] = _("show log message timestamps");
}
nochangeix = choicecnt;
choicestr[ choicecnt++ ] = _("continue");
assert( choicecnt <= CHOICEMAX );
responseix = dlog_multi_query( querystr,
querycnt,
choicestr,
choicecnt,
0, /* hilitestr */
IXMAX, /* hiliteix */
0, /* defaultstr */
nochangeix, /* defaultix */
DLOG_TIMEOUT,
nochangeix, /* timeout ix */
nochangeix, /* sigint ix */
nochangeix, /* sighup ix */
nochangeix);/* sigquit ix */
ackcnt = 0;
if ( responseix == progix ) {
char buf[ 10 ];
const size_t ncix = 1;
const size_t okix = 2;
ackstr[ ackcnt++ ] = "\n";
dlog_multi_ack( ackstr,
ackcnt );
ackcnt = 0;
responseix = dlog_string_query( prompt_prog_cb,
0,
buf,
sizeof( buf ),
DLOG_TIMEOUT,
ncix,/* timeout ix */
ncix, /* sigint ix */
ncix, /* sighup ix */
ncix, /* sigquit ix */
okix );
if ( responseix == okix ) {
int newinterval;
newinterval = atoi( buf );
if ( ! strlen( buf )) {
ackstr[ ackcnt++ ] = _("no change\n");
} else if ( newinterval > 0 ) {
time32_t newdeadline;
char intervalbuf[ 64 ];
newdeadline = time( 0 ) + ( time32_t )newinterval;
if ( progrpt_enabledpr ) {
if ( ( time32_t )newinterval == progrpt_interval ) {
ackstr[ ackcnt++ ] = _("no change\n");
} else {
ackstr[ ackcnt++ ] = _("changing progress report interval to ");
sprintf( intervalbuf,
_("%d seconds\n"),
newinterval );
assert( strlen( intervalbuf )
<
sizeof( intervalbuf ));
ackstr[ ackcnt++ ] = intervalbuf;
if ( progrpt_deadline > newdeadline ) {
progrpt_deadline = newdeadline;
}
}
} else {
ackstr[ ackcnt++ ] = _("enabling progress reports at ");
sprintf( intervalbuf,
_("%d second intervals\n"),
newinterval );
assert( strlen( intervalbuf )
<
sizeof( intervalbuf ));
ackstr[ ackcnt++ ] = intervalbuf;
progrpt_enabledpr = BOOL_TRUE;
progrpt_deadline = newdeadline;
}
progrpt_interval = ( time32_t )newinterval;
} else {
if ( progrpt_enabledpr ) {
ackstr[ ackcnt++ ] = _("disabling progress reports\n");
} else {
ackstr[ ackcnt++ ] = _("no change\n");
}
progrpt_enabledpr = BOOL_FALSE;
}
} else {
ackstr[ ackcnt++ ] = _("no change\n");
}
} else if ( responseix == mllevix ) {
mlog_showlevel = ! mlog_showlevel;
if ( mlog_showlevel ) {
ackstr[ ackcnt++ ] = _("showing log message levels\n");
} else {
ackstr[ ackcnt++ ] = _("hiding log message levels\n");
}
} else if ( responseix == mlssix ) {
mlog_showss = ! mlog_showss;
if ( mlog_showss ) {
ackstr[ ackcnt++ ] = _("showing log message subsystems\n");
} else {
ackstr[ ackcnt++ ] = _("hiding log message subsystems\n");
}
} else if ( responseix == mltsix ) {
mlog_timestamp = ! mlog_timestamp;
if ( mlog_timestamp ) {
ackstr[ ackcnt++ ] = _("showing log message timestamps\n");
} else {
ackstr[ ackcnt++ ] = _("hiding log message timestamps\n");
}
}
dlog_multi_ack( ackstr,
ackcnt );
} else {
ackcnt = 0;
ackstr[ ackcnt++ ] = _("continuing\n");
dlog_multi_ack( ackstr,
ackcnt );
}
fold_init( fold, _("end dialog"), '-' );
postamblecnt = 0;
postamblestr[ postamblecnt++ ] = "\n";
postamblestr[ postamblecnt++ ] = fold;
postamblestr[ postamblecnt++ ] = "\n\n";
assert( postamblecnt <= POSTAMBLEMAX );
dlog_end( postamblestr,
postamblecnt );
return stop_requested;
}
static char *
sigintstr( void )
{
int ttyfd;
static char buf[ 20 ];
struct termios termios;
cc_t intchr;
int rval;
ttyfd = dlog_fd( );
if ( ttyfd == -1 ) {
return 0;
}
rval = tcgetattr( ttyfd, &termios );
if ( rval ) {
mlog( MLOG_NITTY | MLOG_PROC,
"could not get controlling terminal information: %s\n",
strerror( errno ));
return 0;
}
intchr = termios.c_cc[ VINTR ];
mlog( MLOG_NITTY | MLOG_PROC,
"tty fd: %d; terminal interrupt character: %c (0%o)\n",
ttyfd,
intchr,
intchr );
if ( intchr < ' ' ) {
sprintf( buf, "^%c", intchr + '@' );
} else if ( intchr == 0177 ) {
sprintf( buf, "DEL" );
} else {
sprintf( buf, "%c", intchr );
}
assert( strlen( buf ) < sizeof( buf ));
return buf;
}
#ifdef DUMP
static bool_t
set_rlimits( void )
#endif /* DUMP */
#ifdef RESTORE
static bool_t
set_rlimits( size64_t *vmszp )
#endif /* RESTORE */
{
struct rlimit64 rlimit64;
#ifdef RESTORE
size64_t vmsz;
#endif /* RESTORE */
/* REFERENCED */
int rval;
assert( minstacksz <= maxstacksz );
rval = getrlimit64( RLIMIT_AS, &rlimit64 );
assert( ! rval );
mlog( MLOG_NITTY | MLOG_NOLOCK | MLOG_PROC,
"RLIMIT_AS org cur 0x%llx max 0x%llx\n",
rlimit64.rlim_cur,
rlimit64.rlim_max );
#ifdef RESTORE
if (rlimit64.rlim_cur != RLIM64_INFINITY) {
rlimit64.rlim_cur = rlimit64.rlim_max;
( void )setrlimit64( RLIMIT_AS, &rlimit64 );
rval = getrlimit64( RLIMIT_AS, &rlimit64 );
assert( ! rval );
mlog( MLOG_NITTY | MLOG_NOLOCK | MLOG_PROC,
"RLIMIT_VMEM now cur 0x%llx max 0x%llx\n",
rlimit64.rlim_cur,
rlimit64.rlim_max );
}
vmsz = ( size64_t )rlimit64.rlim_cur;
#endif /* RESTORE */
assert( minstacksz <= maxstacksz );
rval = getrlimit64( RLIMIT_STACK, &rlimit64 );
assert( ! rval );
mlog( MLOG_NITTY | MLOG_NOLOCK | MLOG_PROC,
"RLIMIT_STACK org cur 0x%llx max 0x%llx\n",
rlimit64.rlim_cur,
rlimit64.rlim_max );
if ( rlimit64.rlim_cur < minstacksz ) {
if ( rlimit64.rlim_max < minstacksz ) {
mlog( MLOG_DEBUG
|
MLOG_NOLOCK
|
MLOG_PROC,
"raising stack size hard limit "
"from 0x%llx to 0x%llx\n",
rlimit64.rlim_max,
minstacksz );
rlimit64.rlim_cur = minstacksz;
rlimit64.rlim_max = minstacksz;
( void )setrlimit64( RLIMIT_STACK, &rlimit64 );
rval = getrlimit64( RLIMIT_STACK, &rlimit64 );
assert( ! rval );
if ( rlimit64.rlim_cur < minstacksz ) {
mlog( MLOG_NORMAL
|
MLOG_WARNING
|
MLOG_NOLOCK
|
MLOG_PROC,
_("unable to raise stack size hard limit "
"from 0x%llx to 0x%llx\n"),
rlimit64.rlim_max,
minstacksz );
}
} else {
mlog( MLOG_DEBUG
|
MLOG_NOLOCK
|
MLOG_PROC,
"raising stack size soft limit "
"from 0x%llx to 0x%llx\n",
rlimit64.rlim_cur,
minstacksz );
rlimit64.rlim_cur = minstacksz;
( void )setrlimit64( RLIMIT_STACK, &rlimit64 );
rval = getrlimit64( RLIMIT_STACK, &rlimit64 );
assert( ! rval );
if ( rlimit64.rlim_cur < minstacksz ) {
mlog( MLOG_NORMAL
|
MLOG_WARNING
|
MLOG_NOLOCK
|
MLOG_PROC,
_("unable to raise stack size soft limit "
"from 0x%llx to 0x%llx\n"),
rlimit64.rlim_cur,
minstacksz );
}
}
} else if ( rlimit64.rlim_cur > maxstacksz ) {
mlog( MLOG_DEBUG
|
MLOG_NOLOCK
|
MLOG_PROC,
"lowering stack size soft limit "
"from 0x%llx to 0x%llx\n",
rlimit64.rlim_cur,
maxstacksz );
rlimit64.rlim_cur = maxstacksz;
( void )setrlimit64( RLIMIT_STACK, &rlimit64 );
rval = getrlimit64( RLIMIT_STACK, &rlimit64 );
assert( ! rval );
if ( rlimit64.rlim_cur > maxstacksz ) {
mlog( MLOG_NORMAL
|
MLOG_WARNING
|
MLOG_NOLOCK
|
MLOG_PROC,
_("unable to lower stack size soft limit "
"from 0x%llx to 0x%llx\n"),
rlimit64.rlim_cur,
maxstacksz );
}
}
mlog( MLOG_NITTY | MLOG_NOLOCK | MLOG_PROC,
"RLIMIT_STACK new cur 0x%llx max 0x%llx\n",
rlimit64.rlim_cur,
rlimit64.rlim_max );
rval = getrlimit64( RLIMIT_DATA, &rlimit64 );
assert( ! rval );
mlog( MLOG_NITTY | MLOG_NOLOCK | MLOG_PROC,
"RLIMIT_DATA org cur 0x%llx max 0x%llx\n",
rlimit64.rlim_cur,
rlimit64.rlim_max );
rval = getrlimit64( RLIMIT_FSIZE, &rlimit64 );
assert( ! rval );
mlog( MLOG_NITTY | MLOG_NOLOCK | MLOG_PROC,
"RLIMIT_FSIZE org cur 0x%llx max 0x%llx\n",
rlimit64.rlim_cur,
rlimit64.rlim_max );
rlimit64.rlim_cur = rlimit64.rlim_max;
( void )setrlimit64( RLIMIT_FSIZE, &rlimit64 );
rlimit64.rlim_cur = RLIM64_INFINITY;
( void )setrlimit64( RLIMIT_FSIZE, &rlimit64 );
rval = getrlimit64( RLIMIT_FSIZE, &rlimit64 );
assert( ! rval );
mlog( MLOG_NITTY | MLOG_NOLOCK | MLOG_PROC,
"RLIMIT_FSIZE now cur 0x%llx max 0x%llx\n",
rlimit64.rlim_cur,
rlimit64.rlim_max );
rval = getrlimit64( RLIMIT_CPU, &rlimit64 );
assert( ! rval );
mlog( MLOG_NITTY | MLOG_NOLOCK | MLOG_PROC,
"RLIMIT_CPU cur 0x%llx max 0x%llx\n",
rlimit64.rlim_cur,
rlimit64.rlim_max );
rlimit64.rlim_cur = rlimit64.rlim_max;
( void )setrlimit64( RLIMIT_CPU, &rlimit64 );
rval = getrlimit64( RLIMIT_CPU, &rlimit64 );
assert( ! rval );
mlog( MLOG_NITTY | MLOG_NOLOCK | MLOG_PROC,
"RLIMIT_CPU now cur 0x%llx max 0x%llx\n",
rlimit64.rlim_cur,
rlimit64.rlim_max );
#ifdef RESTORE
*vmszp = vmsz;
#endif /* RESTORE */
return BOOL_TRUE;
}
struct sig_printmap {
int num;
char *string;
};
typedef struct sig_printmap sig_printmap_t;
static sig_printmap_t sig_printmap[ ] = {
{SIGHUP, "SIGHUP"},
{SIGINT, "SIGINT"},
{SIGQUIT, "SIGQUIT"},
{SIGILL, "SIGILL"},
{SIGABRT, "SIGABRT"},
{SIGFPE, "SIGFPE"},
{SIGBUS, "SIGBUS"},
{SIGSEGV, "SIGSEGV"},
#ifdef SIGSYS
{SIGSYS, "SIGSYS"},
#endif
{SIGPIPE, "SIGPIPE"},
{SIGALRM, "SIGALRM"},
{SIGTERM, "SIGTERM"},
{SIGUSR1, "SIGUSR1"},
{SIGUSR2, "SIGUSR2"},
{SIGCLD, "SIGCLD"},
{SIGPWR, "SIGPWR"},
{SIGURG, "SIGURG"},
{SIGPOLL, "SIGPOLL"},
{SIGXCPU, "SIGXCPU"},
{SIGXFSZ, "SIGXFSZ"},
#if HIDDEN
{SIGRTMIN, "SIGRTMIN"},
{SIGRTMAX, "SIGRTMAX"},
#endif
{0, "???"}
};
static char *
sig_numstring( int num )
{
sig_printmap_t *p = sig_printmap;
sig_printmap_t *endp = sig_printmap
+
( sizeof( sig_printmap )
/
sizeof( sig_printmap[ 0 ] ));
for ( ; p < endp ; p++ ) {
if ( p->num == num ) {
return p->string;
}
}
return "???";
}
static char *
strpbrkquotes( char *p, const char *sep )
{
bool_t prevcharwasbackslash = BOOL_FALSE;
bool_t inquotes = BOOL_FALSE;
for ( ; ; p++ ) {
if ( *p == 0 ) {
return 0;
}
if ( *p == '\\' ) {
if ( ! prevcharwasbackslash ) {
prevcharwasbackslash = BOOL_TRUE;
} else {
prevcharwasbackslash = BOOL_FALSE;
}
continue;
}
if ( *p == '"' ) {
if ( prevcharwasbackslash ) {
prevcharwasbackslash = BOOL_FALSE;
continue;
}
if ( inquotes ) {
inquotes = BOOL_FALSE;
} else {
inquotes = BOOL_TRUE;
}
continue;
}
if ( ! inquotes ) {
if ( strchr( sep, ( int )( *p ))) {
return p;
}
}
prevcharwasbackslash = BOOL_FALSE;
}
/* NOTREACHED */
}
static char *
stripquotes( char *p )
{
size_t len = strlen( p );
char *endp;
char *nextp;
bool_t justremovedbackslash;
if ( len > 2 && p[ 0 ] == '"' ) {
p++;
len--;
if ( len && p[ len - 1 ] == '"' ) {
p[ len - 1 ] = 0;
len--;
}
}
endp = p + len;
justremovedbackslash = BOOL_FALSE;
for ( nextp = p ; nextp < endp ; ) {
if ( *nextp == '\\' && ! justremovedbackslash ) {
shiftleftby1( nextp, endp );
endp--;
justremovedbackslash = BOOL_TRUE;
} else {
justremovedbackslash = BOOL_FALSE;
nextp++;
}
}
return p;
}
static void
shiftleftby1( char *p, char *endp )
{
for ( ; p < endp ; p++ ) {
*p = p[ 1 ];
}
}