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