blob: 032e6f71a73de8d6c21354829d361d67fa5c1558 [file] [log] [blame]
/*
* Copyright (c) 2000-2001 Silicon Graphics, Inc.
* All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <assert.h>
#include <string.h>
#include "config.h"
#include "types.h"
#include "mlog.h"
#include "dlog.h"
#include "getopt.h"
static int dlog_ttyfd = -1;
static volatile bool_t dlog_allowed_flag = BOOL_FALSE;
static bool_t dlog_timeouts_flag = BOOL_FALSE;
static char *promptstr = " -> ";
static sigset_t dlog_registered_sigs;
static bool_t promptinput( char *buf,
size_t bufsz,
ix_t *exceptionixp,
time32_t timeout,
ix_t timeoutix,
ix_t sigintix,
ix_t sighupix,
ix_t sigquitix,
char *fmt, ... );
static void dlog_string_query_print( void *ctxp, char *fmt, ... );
bool_t
dlog_init( int argc, char *argv[ ] )
{
int c;
/* can only call once
*/
assert( dlog_ttyfd == -1 );
/* initially allow dialog, use stdin fd
*/
dlog_ttyfd = 0; /* stdin */
dlog_allowed_flag = BOOL_TRUE;
dlog_timeouts_flag = BOOL_TRUE;
sigemptyset( &dlog_registered_sigs );
/* look for command line option claiming the operator knows
* what's up.
*/
optind = 1;
opterr = 0;
while ( ( c = getopt( argc, argv, GETOPT_CMDSTRING )) != EOF ) {
switch ( c ) {
case GETOPT_FORCE:
dlog_ttyfd = -1;
dlog_allowed_flag = BOOL_FALSE;
break;
case GETOPT_NOTIMEOUTS:
dlog_timeouts_flag = BOOL_FALSE;
break;
}
}
#ifdef RESTORE
/* look to see if restore source coming in on
* stdin. If so , try to open /dev/tty for dialogs.
*/
if ( optind < argc && ! strcmp( argv[ optind ], "-" )) {
dlog_ttyfd = open( "/dev/tty", O_RDWR );
if ( dlog_ttyfd < 0 ) {
perror("/dev/tty");
dlog_ttyfd = -1;
dlog_allowed_flag = BOOL_FALSE;
}
}
#endif /* RESTORE */
#ifdef CHKSTDIN
/* if stdin is not a tty, don't allow dialogs
*/
if ( dlog_allowed_flag ) {
struct stat statbuf;
int rval;
assert( dlog_ttyfd >= 0 );
rval = fstat( dlog_ttyfd, &statbuf );
if ( rval ) {
mlog( MLOG_VERBOSE | MLOG_WARNING,
_("could not fstat stdin (fd %d): %s (%d)\n"),
dlog_ttyfd,
strerror( errno ),
errno );
} else {
mlog( MLOG_DEBUG,
"stdin mode 0x%x\n",
statbuf.st_mode );
}
}
#endif /* CHKSTDIN */
return BOOL_TRUE;
}
bool_t
dlog_allowed( void )
{
return dlog_allowed_flag;
}
void
dlog_desist( void )
{
dlog_allowed_flag = BOOL_FALSE;
}
int
dlog_fd( void )
{
return dlog_ttyfd;
}
void
dlog_begin( char *preamblestr[ ], size_t preamblecnt )
{
size_t ix;
mlog_lock( );
for ( ix = 0 ; ix < preamblecnt ; ix++ ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
preamblestr[ ix ] );
}
}
void
dlog_end( char *postamblestr[ ], size_t postamblecnt )
{
size_t ix;
for ( ix = 0 ; ix < postamblecnt ; ix++ ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
postamblestr[ ix ] );
}
mlog_unlock( );
}
ix_t
dlog_multi_query( char *querystr[ ],
size_t querycnt,
char *choicestr[ ],
size_t choicecnt,
char *hilitestr,
size_t hiliteix,
char *defaultstr,
ix_t defaultix,
time32_t timeout,
ix_t timeoutix,
ix_t sigintix,
ix_t sighupix,
ix_t sigquitix )
{
size_t ix;
char buf[ 100 ];
char *prepromptstr;
/* sanity
*/
assert( dlog_allowed_flag );
/* display query description strings
*/
for ( ix = 0 ; ix < querycnt ; ix++ ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
querystr[ ix ] );
}
/* display the choices: NOTE: display is 1-based, code intfs 0-based!
*/
for ( ix = 0 ; ix < choicecnt ; ix++ ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
"%u: %s",
ix + 1,
choicestr[ ix ] );
if ( ix == hiliteix ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
"%s",
hilitestr ? hilitestr : " *" );
}
if ( ix == defaultix ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
"%s",
defaultstr ? defaultstr : _(" (default)") );
}
if ( dlog_timeouts_flag
&&
timeoutix != IXMAX
&&
ix == timeoutix ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
_(" (timeout in %u sec)"),
timeout );
}
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
"\n" );
}
/* read the tty until we get a proper answer or are interrupted
*/
prepromptstr = "";
for ( ; ; ) {
ix_t exceptionix;
bool_t ok;
/* prompt and accept input
*/
ok = promptinput( buf,
sizeof( buf ),
&exceptionix,
timeout,
timeoutix,
sigintix,
sighupix,
sigquitix,
prepromptstr,
choicecnt );
if ( ok ) {
long int val;
char *end = buf;
if ( ! strlen( buf )) {
return defaultix;
}
val = strtol( buf, &end, 10 );
if ( *end != '\0' || val < 1 || val > choicecnt ) {
prepromptstr = _(
"please enter a value "
"between 1 and %d inclusive ");
continue;
}
return val - 1; // return value is a 0-based index
} else {
return exceptionix;
}
}
/* NOTREACHED */
}
void
dlog_multi_ack( char *ackstr[ ], size_t ackcnt )
{
size_t ix;
for ( ix = 0 ; ix < ackcnt ; ix++ ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
ackstr[ ix ] );
}
}
ix_t
dlog_string_query( dlog_ucbp_t ucb, /* user's print func */
void *uctxp, /* user's context for above */
char *bufp, /* typed string returned in */
size_t bufsz, /* buffer size */
time32_t timeout, /* secs b4 giving up */
ix_t timeoutix,
ix_t sigintix,
ix_t sighupix,
ix_t sigquitix,
ix_t okix )
{
ix_t exceptionix;
bool_t ok;
/* sanity
*/
assert( dlog_allowed_flag );
/* call the caller's callback with his context, print context, and
* print operator
*/
( * ucb )( uctxp, dlog_string_query_print, 0 );
/* if called for, print the timeout and a newline.
* if not, print just a newline
*/
if ( dlog_timeouts_flag && timeoutix != IXMAX ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
_(" (timeout in %u sec)\n"),
timeout );
} else {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
"\n" );
}
/* prompt and accept input
*/
ok = promptinput( bufp,
bufsz,
&exceptionix,
timeout,
timeoutix,
sigintix,
sighupix,
sigquitix,
"" );
if ( ok ) {
return okix;
} else {
return exceptionix;
}
}
void
dlog_string_ack( char *ackstr[ ], size_t ackcnt )
{
dlog_multi_ack( ackstr, ackcnt );
}
/* ok that this is a static, since used under mutual exclusion lock
*/
static volatile int dlog_signo_received;
bool_t
dlog_sighandler( int signo )
{
if ( sigismember( &dlog_registered_sigs, signo ) < 1 )
return BOOL_FALSE;
// only process the first signal
sigemptyset( &dlog_registered_sigs );
dlog_signo_received = signo;
return BOOL_TRUE;
}
/* ARGSUSED */
static void
dlog_string_query_print( void *ctxp, char *fmt, ... )
{
va_list args;
assert( ! ctxp );
va_start( args, fmt );
mlog_va( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE, fmt, args );
va_end( args );
}
static bool_t
promptinput( char *buf,
size_t bufsz,
ix_t *exceptionixp,
time32_t timeout,
ix_t timeoutix,
ix_t sigintix,
ix_t sighupix,
ix_t sigquitix,
char *fmt,
... )
{
va_list args;
time32_t now = time( NULL );
int nread = -1;
sigset_t orig_set;
char *bufp = buf;
/* display the pre-prompt
*/
va_start( args, fmt );
mlog_va( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE, fmt, args );
va_end( args );
/* display standard prompt
*/
#ifdef NOTYET
if ( dlog_timeouts_flag && timeoutix != IXMAX ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
_("(timeout in %d sec)"),
timeout );
}
#endif /* NOTYET */
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE, promptstr );
/* set up timeout
*/
if ( dlog_timeouts_flag && timeoutix != IXMAX ) {
timeout += now;
} else {
timeout = TIMEMAX;
}
/* set up signal handling
* the mlog lock is held for the life of the dialog (see dlog_begin)
* and it's possible the main thread, which normally does the signal
* handling, is now waiting on the mlog lock trying to log a message.
* so unblock the relevant signals for this thread. note this means
* the current thread or the main thread might handle one of these
* signals.
*/
dlog_signo_received = -1;
sigemptyset( &dlog_registered_sigs );
if ( sigintix != IXMAX ) {
sigaddset( &dlog_registered_sigs, SIGINT );
}
if ( sighupix != IXMAX ) {
sigaddset( &dlog_registered_sigs, SIGHUP );
sigaddset( &dlog_registered_sigs, SIGTERM );
}
if ( sigquitix != IXMAX ) {
sigaddset( &dlog_registered_sigs, SIGQUIT );
}
pthread_sigmask( SIG_UNBLOCK, &dlog_registered_sigs, &orig_set );
/* wait for input, timeout, or interrupt.
* note we come out of the select() frequently in order to
* check for a signal. the signal may have been handled by the
* the main thread, so we can't rely on the signal waking us
* up from the select().
*/
while ( now < timeout && dlog_signo_received == -1 && dlog_allowed_flag ) {
int rc;
fd_set rfds;
struct timeval tv = { 0, 100000 }; // 100 ms
FD_ZERO( &rfds );
FD_SET( dlog_ttyfd, &rfds );
rc = select( dlog_ttyfd + 1, &rfds, NULL, NULL, &tv );
if ( rc > 0 && FD_ISSET( dlog_ttyfd, &rfds ) ) {
nread = read( dlog_ttyfd, bufp, bufsz );
if ( nread < 0 ) {
break; // error handled below
} else if ( nread == 0 && buf == bufp ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE, "\n" );
*bufp = 0;
break; // no input, return an empty string
} else if ( nread > 0 && bufp[nread-1] == '\n' ) {
// received a full line, chomp the newline
bufp[nread-1] = 0;
break;
} else if ( nread == bufsz ) {
// no more room, truncate and return
bufp[nread-1] = 0;
break;
}
// keep waiting for a full line of input
bufp += nread;
bufsz -= nread;
nread = -1;
}
now = time( NULL );
}
/* restore signal handling
*/
pthread_sigmask( SIG_SETMASK, &orig_set, NULL );
sigemptyset( &dlog_registered_sigs );
/* check for timeout or interrupt
*/
if ( nread < 0 ) {
if ( now >= timeout ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
_("timeout\n") );
*exceptionixp = timeoutix;
} else if ( dlog_signo_received == SIGINT ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
_("keyboard interrupt\n") );
mlog_exit_hint(RV_KBD_INTR);
*exceptionixp = sigintix;
} else if ( dlog_signo_received == SIGHUP ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
_("hangup\n") );
*exceptionixp = sighupix;
} else if ( dlog_signo_received == SIGTERM ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
_("terminate\n") );
*exceptionixp = sighupix;
} else if ( dlog_signo_received == SIGQUIT ) {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
_("keyboard quit\n") );
mlog_exit_hint(RV_KBD_INTR);
*exceptionixp = sigquitix;
} else {
mlog( MLOG_NORMAL | MLOG_NOLOCK | MLOG_BARE,
_("abnormal dialog termination\n"));
*exceptionixp = sigquitix;
}
return BOOL_FALSE;
} else {
assert( dlog_signo_received == -1 );
*exceptionixp = 0;
return BOOL_TRUE;
}
}