/*
 * 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;

	/* 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 ) {
		return mlog_exit(EXIT_ERROR, RV_INIT);
	}

	/* 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 ) {
			return mlog_exit(EXIT_ERROR, RV_INIT);
		}
		ok = drive_init3( );
		if ( ! ok ) {
			return mlog_exit(EXIT_ERROR, RV_INIT);
		}
#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 */
			return mlog_exit(exitcode, RV_UNKNOWN);
		} else if ( content_complete( )) {
			return mlog_exit(EXIT_NORMAL, RV_OK);
		} else {
			return mlog_exit(EXIT_INTERRUPT, RV_UNKNOWN);
		}
	}

	/* 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);
		}
	}
	return mlog_exit(exitcode, RV_UNKNOWN);
}

#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 ];
	}
}
