/* main() - command parsing and dispatch */ /* * This is part of ODS2 written by Paul Nankervis, * email address: Paulnank@au1.ibm.com * * ODS2 is distributed freely for all members of the * VMS community to use. However all derived works * must maintain comments in their source to acknowledge * the contributions of the original author and * subsequent contributors. This is free software; no * warranty is offered, and while we believe it to be useful, * you use it at your own risk. * * The modules in ODS2 are:- * * ACCESS.C Routines for accessing ODS2 disks * CACHE.C Routines for managing memory cache * COMPAT.C Routines for OS compatibility * DEVICE.C Routines to maintain device information * DIRECT.C Routines for handling directories * ODS2.C The mainline program * *CMD.C Routines to execute CLI commands * PHYVMS.C Routine to perform physical I/O * PHYVHD.C Interface to libvhd for VHD format virtual disks. * PHYVIRT.C Routines for managing virtual disks. * RMS.C Routines to handle RMS structures * SYSMSG.C Routines to convert status codes to text * UPDATE.C Routines for updating ODS2 structures * VMSTIME.C Routines to handle VMS times * * On non-VMS platforms PHYVMS.C should be replaced as follows:- * * Unix PHYUNIX.C * OS/2 PHYOS2.C * Windows 95/NT PHYNT.C * * The included make/mms/com files do all this for you. * * For accessing disk images (e.g. .ISO or simulator files), * simply mount /image (or /virtual) filename. * * See version.h and git for revision history. */ /* This is the top level set of routines. It is fairly * simple minded asking the user for a command, doing some * primitive command parsing, and then calling a set of routines * to perform whatever function is required (for example COPY). * Some routines are implemented in different ways to test the * underlying routines. */ #include "version.h" #if !defined( DEBUG ) && defined( DEBUG_ODS2 ) #define DEBUG DEBUG_ODS2 #else #ifndef DEBUG #define DEBUG 0 #endif #endif #ifndef MAXATDEPTH #define MAXATDEPTH 16 #endif #ifdef VMS #ifdef __DECC #pragma module MODULE_NAME MODULE_IDENT #else #ifdef VAXC #module MODULE_NAME MODULE_IDENT #endif /* VAXC */ #endif /* __DECC */ #include /* For VMS version of getcmd() */ #include /* SMG$_EOF */ #include #else #include #endif /* VMS */ #ifndef _WIN32 #include #endif #define DEBUGx on #include #include "version.h" #include "cmddef.h" #include "cache.h" #include "compat.h" #include "descrip.h" #include "ods2.h" #include "phyvirt.h" #include "ssdef.h" #include "stsdef.h" #include "sysmsg.h" #ifdef VAXC #ifdef EXIT_SUCCESS #undef EXIT_SUCCESS #endif #define EXIT_SUCCESS SS$_NORMAL #endif #if defined( _WIN32 ) && defined(_MSC_VER) && defined( DEBUG_BUILD ) && defined( USE_VLD ) /* Normally done in the project file, but VLD is optional and I'd rather not provide * instructions as they vary by IDE version. See http://vld.codeplex.com/ if interested. */ #include "C:\\Program Files (x86)\\Visual Leak Detector\\include\\vld.h" #pragma comment (lib,"C:\\Program Files (x86)\\Visual Leak Detector\\lib\\Win32\\vld.lib") int vld_present = 1; #else int vld_present = 0; #endif #ifndef CMD_HISTSIZE #define CMD_HISTSIZE (200) #endif uint32_t cmd_histsize = CMD_HISTSIZE; #ifdef USE_LIBEDIT #include EditLine *editline; History *edithist; static char *get_prompt( EditLine *el ) { UNUSED(el); return ( qstyle_c == '/' ? MNAME( MODULE_NAME ) "$> " : MNAME( MODULE_NAME ) "-> " ); } #else FILE *histfile = NULL; #endif typedef struct atfile { FILE *fp; uint32_t line; } atfile_t; int maxatdepth = MAXATDEPTH; static void killat( int *atc, void **atfiles ); static int disktypecmp( const void *da, const void *db ); static vmscond_t cmdsplit( char *str ); static vmscond_t addarg( int *argc, void ***argv, void *arg ); static vmscond_t cmdexecute( int argc, char **argv, int qualc, char **qualv ); DECL_CMD(copy); DECL_CMD(create); DECL_CMD(delete); DECL_CMD(diff); DECL_CMD(dir); DECL_CMD(dismount); static DECL_CMD(echo); DECL_CMD(extend); DECL_CMD(help); DECL_CMD(initial); DECL_CMD(mount); DECL_CMD(rename); DECL_CMD(search); DECL_CMD(set); DECL_CMD(show); DECL_CMD(spawn); DECL_CMD(type); #ifdef VMS static char *getcmd( char *inp, size_t max, char *prompt ); #endif static void add_diskkwds( qualp_t qualset, const char *qname, qualp_t *disks ); /* Qualifier style = vms => /qualifier; else -option */ const char *qstyle_s = "/"; int interactive = TRUE; int verify_cmd = FALSE; int error_exit = FALSE; int exit_status = EXIT_SUCCESS; /******************************************************************* Command table */ /* information about the commands we know... */ extern qual_t copyquals[], createquals[], delquals[], diffquals[], dirquals[], dmoquals[], extendquals[], iniquals[], mouquals[], renamequals[], searchquals[], typequals[]; extern param_t copypars[], createpars[], delpars[], diffpars[], dirpars[], dmopars[], extendpars[], helppars[], inipars[], moupars[], renamepars[], searchpars[], setpars[], showpars[], typepars[]; static param_t echopars[] = { {"string", REQ | NOLIM, STRING, NOPA, "String(s) to print"}, {NULL, 0, 0, NOPA, NULL} }; extern char spawnhelp[]; CMDSET_t maincmds[] = { { "copy", docopy, 0, copyquals, copypars, "commands copy" }, { "create", docreate, 0, createquals, createpars, "commands create" }, { "delete", dodelete, 0, delquals, delpars, "commands delete" }, { "difference", dodiff, 0, diffquals, diffpars, "commands difference" }, { "directory", dodir, 0, dirquals, dirpars, "commands directory" }, { "dismount", dodismount, 0, dmoquals, dmopars, "commands dismount" }, { "echo", doecho, 0, NULL, echopars, "commands echo" }, { "exit", NULL, 2, NULL, NULL, "commands exit" }, { "extend", doextend, 0, NULL, extendpars, "commands extend" }, { "help", dohelp, 0, NULL, helppars, "commands help" }, { "initialize", doinitial, -4, iniquals, inipars, "commands initialize" }, { "mount", domount, 0, mouquals, moupars, "commands mount" }, { "quit", NULL, 2, NULL, NULL, "commmands quit" }, { "rename", dorename, -3, renamequals,renamepars, "commands rename" }, { "search", dosearch, 0, searchquals,searchpars, "commands search" }, { "set", doset, 0, NULL, setpars, "commands set" }, { "show", doshow, 0, NULL, showpars, "commands show" }, { "spawn", dospawn, 0, NULL, NULL, spawnhelp }, { "type", dotype, 0, typequals, typepars, "commands type" }, { NULL, NULL, 0, NULL, NULL, NULL } /* ** END MARKER ** */ }; /*********************************************************** main() */ int main( int argc, char **argv ) { vmscond_t sts; int atc; void **atfiles; int initfile; char *rl = NULL, *buf = NULL; size_t bufsize = 80; qualp_t disks = NULL; char *p, mname[] = { MNAME(MODULE_NAME) }; #ifdef VMS #ifndef VMS_MAXCMDLEN #define VMS_MAXCMDLEN 2048 #endif char *vmscmdbuf; #endif char *hfname = NULL; #ifdef USE_LIBEDIT struct HistEvent histevt; #endif signal( SIGINT, SIG_IGN ); #ifndef VMS { /* "Standard"s... */ const char *const loc[] = { #ifdef PREFERRED_LOCALE PREFERRED_LOCALE, #endif #ifdef _WIN32 ".28591", #else "ISO-8859-1", "iso-8859-1", "iso_8859_1", ".iso88591", ".ISO88591", ".iso8859-1", ".iso-8859-1", ".iso_8859-1", "en_US.ISO-8859-1", "en_US.ISO_8859-1", "en_US.iso88591", "en_US.ISO88591", #endif "C", NULL }, *const*p; for( p = loc; *p ; p++ ) { #if DEBUG_LOCALE char *q; if( (q = setlocale( LC_CTYPE, *p )) ) printf( "%s => %s [OK]\n", *p, q ); else printf( "%s => [Failed]\n", *p ); #else if( setlocale( LC_CTYPE, *p ) != NULL ) { #ifdef USE_LIBEDIT setenv( "LC_CTYPE", *p, 1 ); unsetenv( "LC_ALL" ); #endif break; } #endif } #if !DEBUG_LOCALE if( !*p ) { int err; err = errno; printmsg( ODS2_LOCALE, 0 ); printmsg( $SETLEVEL(ODS2_OSERROR,WARNING), MSG_CONTINUE, ODS2_LOCALE, strerror( err ) ); } #endif } #endif if( $FAILS( (sts = sethelpfile( NULL, "ODS2HELP", argv[0] )) ) ) { printmsg( sts, 0 ); } atc = 0; if( (atfiles = (void **) calloc( 1, sizeof( void * ) )) == NULL ) { printmsg( ODS2_INITIALFAIL, 0 ); exit( EXIT_FAILURE ); } for( p = mname; *p; p++ ) *p = tolower( *p ); #ifdef USE_LIBEDIT edithist = history_init(); editline = el_init(mname,stdin, stdout, stderr); el_set( editline, EL_EDITOR, "emacs" ); el_set( editline, EL_SIGNAL, 1 ); el_set( editline, EL_PROMPT, get_prompt ); el_set( editline, EL_HIST, history, edithist ); history( edithist, &histevt, H_SETSIZE, cmd_histsize ); el_source( editline, NULL ); if( (hfname = homefile( 1, mname, NULL)) != NULL ) { history( edithist, &histevt, H_LOAD, hfname ); } #else hfname = homefile( 1, mname, ".history" ); if( hfname) histfile = openf( hfname, "a+" ); if( histfile && Chmod( hfname, S_IREAD | S_IWRITE ) ) perror( "chmod history" ); /* if it's possible, load previous session history into recall buffers, if any. * Haven't found a case where it can be done. * In any case, history can be viewed. Or find a libedit for the host OS... */ #endif sts = SS$_NORMAL; { FILE *atfile = NULL; p = get_env( "ODS2INIT" ); if( p != NULL ) { if( strcmp( p, "-" ) == 0 ) { interactive = FALSE; } else { char *rp = NULL; rp = get_realpath( p ); atfile = openf( rp? rp : p, "r" ); if( atfile == NULL ) { int err = errno; printmsg( ODS2_OPENIND, 0, rp? rp : p ); printmsg( ODS2_OSERROR, MSG_CONTINUE, ODS2_OPENIND, strerror( err ) ); } else { #ifdef USE_LIBEDIT char *fn; size_t len; len = strlen( rp? rp : p ) + sizeof( ";ODS2INIT=" ); fn = malloc( len ); if( fn != NULL ) { snprintf( fn, len, ";ODS2INIT=%s", rp? rp : p ); history( edithist, &histevt, H_ENTER, fn ); free( fn ); } #else if( histfile ) fprintf( histfile, ";ODS2INIT=%s\n", rp? rp : p ); #endif } if( rp != NULL ) free( rp ); } free( p ); } if( (p == NULL) && (atfile == NULL) && (p = homefile( 1, mname, ".ini" )) != NULL ) { char *rp = NULL; rp = get_realpath( p ); atfile = openf( rp? rp : p, "r" ); if( atfile == NULL ) { int err = errno; if( errno != ENOENT ) { printmsg( ODS2_OPENIND, 0, rp? rp : p ); printmsg( ODS2_OSERROR, MSG_CONTINUE, ODS2_OPENIND, strerror( err ) ); } } else { #ifdef USE_LIBEDIT char *fn; size_t len; len = strlen( rp? rp : p ) + sizeof( ";initfile=" ); fn = malloc( len ); if( fn != NULL ) { snprintf( fn, len, ";initfile=%s", rp? rp : p ); history( edithist, &histevt, H_ENTER, fn ); free( fn ); } #else if( histfile ) fprintf( histfile, ";initfile=%s\n", rp? rp : p ); #endif } if( rp != NULL ) free( rp ); free( p ); } if( (initfile = (atfile != NULL)) ) { atfile_t *atp; if( (atp = (atfile_t *) malloc( sizeof( atfile_t ) )) == NULL ) { fclose( atfile ); sts = printmsg( ODS2_INITIALFAIL, 0 ); exit( EXIT_FAILURE ); } atp->fp = atfile; atp->line = 0; if( $FAILS(sts = addarg( &atc, &atfiles, atp )) ) { sts = printmsg( ODS2_INITIALFAIL, 0 ); exit( EXIT_FAILURE ); } } } if( $FAILS(sts = sys_initialize()) ) { printmsg( ODS2_INITIALFAIL, 0 ); printmsg( sts, MSG_CONTINUE | MSG_NOARGS, ODS2_INITIALFAIL ); exit( EXIT_FAILURE ); } add_diskkwds( mouquals, DT_NAME, &disks ); add_diskkwds( iniquals, DT_NAME, &disks ); #ifdef VMS if( (vmscmdbuf = malloc( VMS_MAXCMDLEN )) == NULL ) { sts = printmsg( ODS2_INITIALFAIL, 0 ); exit( EXIT_FAILURE ); } #endif argc--; argv++; while( 1 ) { char *ptr = NULL; if( !initfile && argc > 0 ) { size_t n, al = 0; rl = NULL; for( n = 0; n < (unsigned)argc; n++ ) if( !strcmp( "$", argv[n] ) ) break; else al += 1 + strlen( argv[n] ); if( al > 0 ) { size_t i; char *cp; if( buf == NULL || al > bufsize ) { cp = realloc( buf, al ); if( cp == NULL ) { perror( "malloc" ); exit( EXIT_FAILURE ); } buf = cp; bufsize = al; } cp = rl = buf; for( i = 0; i < n; i++ ) { size_t len; len = strlen( *argv ); if( i != 0 ) *cp++ = ' '; memcpy( cp, *argv++, len ); argc--; cp += len; } *cp = '\0'; ptr = rl; #ifdef USE_LIBEDIT if( *ptr && interactive ) history( edithist, &histevt, H_ENTER, ptr ); #else if( *ptr && interactive && histfile ) fprintf( histfile, "%s\n", ptr ); #endif } if( argc > 0 ) { argc--; argv++; } } if( ptr == NULL ) { if( atc ) { atfile_t *atp; atp = (atfile_t *)(atfiles[atc - 1]); if( (rl = fgetline( atp->fp, FALSE, &buf, &bufsize )) == NULL ) { fclose( atp->fp ); free( atfiles[--atc] ); atfiles[atc] = NULL; initfile = FALSE; continue; } else { ++atp->line; ptr = strchr( rl, '\r' ); if( ptr != NULL ) *ptr = '\0'; if( verify_cmd ) printf( "@%u:%u_%c> %s\n", atc, atp->line, (qstyle_c == '/' ? '$' : '-'), rl ); } ptr = rl; } if( ptr == NULL ) { #ifdef VMS if( getcmd( vmscmdbuf, VMS_MAXCMDLEN, (qstyle_c == '/' ? "$> " : "-> ") ) == NULL ) break; ptr = vmscmdbuf; if( histfile ) fprintf( histfile, "%s\n", ptr ); #else #ifdef USE_LIBEDIT if( interactive ) { int nread; rl = (char *) el_gets( editline, &nread ); if( rl == NULL || nread < 1 ) { break; } else { if( rl[nread-1] == '\n' ) --nread; if( buf == NULL || (size_t)nread +1 > bufsize ) { ptr = realloc( buf, nread +1 ); if( ptr == NULL ) { perror( "malloc" ); exit( EXIT_FAILURE ); } buf = ptr; bufsize = nread +1; } memcpy( buf, rl, nread ); buf[nread] = '\0'; ptr = rl = buf; if( *rl != '\0' ) { history( edithist, &histevt, H_ENTER, rl ); } } } else #endif { if( interactive ) printf( "%s%c> ", MNAME(MODULE_NAME), qstyle_c == '/' ? '$' : '-' ); if( (rl = fgetline( stdin, FALSE, &buf, &bufsize )) == NULL ) break; ptr = rl; #ifndef USE_LIBEDIT if( interactive && histfile ) fprintf( histfile, "%s\n", ptr ); #endif } #endif } } while( *ptr == ' ' || *ptr == '\t' ) ptr++; if( !strlen( ptr ) || *ptr == '!' || *ptr == ';' ) continue; if( *ptr == '@' ) { FILE *atfile; char *rp = NULL; if( atc >= maxatdepth ) { sts = printmsg( ODS2_MAXIND, 0, maxatdepth ); argc = 0; killat( &atc, atfiles ); continue; } rp = get_realpath( ptr + 1 ); if( (atfile = openf( rp? rp : ptr + 1, "r" )) == NULL ) { int err; err = errno; printmsg( ODS2_OPENIND, 0, rp? rp : ptr + 1 ); printmsg( ODS2_OSERROR, MSG_CONTINUE, ODS2_OPENIND, strerror( err ) ); argc = 0; killat( &atc, atfiles ); } else { atfile_t *atp; if( (atp = (atfile_t *)malloc( sizeof( atfile_t ) )) == NULL ) { int err; err = errno; sts = printmsg( ODS2_OPENIND, 0, rp? rp : ptr + 1 ); printmsg( ODS2_OSERROR, MSG_CONTINUE, ODS2_OPENIND, strerror( err ) ); fclose( atfile ); argc = 0; killat( &atc, atfiles ); } else { atp->fp = atfile; atp->line = 0; if( $FAILS( sts = addarg( &atc, &atfiles, atp ) ) ) { sts = printmsg( sts, 0 ); if( rp != NULL ) free( rp ); break; } } } if( rp != NULL ) free( rp ); continue; } #if DEBUG if( !isprint( '\347' & 0xFFul ) ) { static int once = 0; if( !once++ ) printf( "***DEBUG: 8-bit characters are not printable, " "probable locale issue - contact maintainer.\n" ); } #endif sts = cmdsplit( ptr ); if( sts == ODS2_EXIT ) break; if( $FAILED(sts) ) { printmsg( sts, MSG_NOARGS ); killat( &atc, atfiles ); initfile = FALSE; argc = 0; if( error_exit ) { exit_status = EXIT_FAILURE; break; } } } /* while 1 */ /* cleanup for exit */ #ifdef USE_LIBEDIT if( hfname != NULL ) { history( edithist, &histevt, H_SAVE, hfname ); history_end( edithist ); el_end( editline ); } #else if( histfile ) { /* Limit history to cmd_histsize lines */ int nent, limit; limit = cmd_histsize; fseek( histfile, 0, SEEK_SET ); for( nent = 0; ; ) { /* Count history lines */ int c; c = getc( histfile ); if( c == EOF ) break; if( c == '\n' ) ++nent; } if( nent > limit ) { /* Over the retention limit, delete oldest lines */ FILE *tmp; char *tmpfile; if( (tmpfile = malloc( strlen( hfname ) + sizeof( ".tmp" ) )) != NULL ) { memcpy( tmpfile, hfname, strlen( hfname ) ); memcpy( tmpfile+strlen( hfname), ".tmp", sizeof( ".tmp" ) ); if( (tmp = openf( tmpfile, "w" )) != NULL ) { if( Chmod( tmpfile, S_IREAD | S_IWRITE ) ) perror( "chmod history" ); limit = nent - limit; fseek( histfile, 0, SEEK_SET ); for( nent = 0; nent < limit; ) { /* Skip old lines */ int c; c = getc( histfile ); if( c == EOF ) break; if( c == '\n' ) ++nent; } for( ; ; nent++ ) { char *line; if( (line = fgetline( histfile, TRUE, &buf, &bufsize )) == NULL ) break; fprintf( tmp, "%s", line ); } fclose( tmp ); #ifdef _WIN32 fclose( histfile ); /* No atomic rename(), newname must not exist. */ histfile = NULL; (void) Unlink( hfname ); #endif if( rename( tmpfile, hfname ) ) perror( "rename history" ); } free( tmpfile ); } } if( histfile ) fclose( histfile ); } #endif if( buf != NULL ) free( buf ); if( hfname != NULL ) free(hfname); killat( &atc, atfiles ); free( atfiles ); free( disks ); exit( exit_status ); } /***************************************************************** killat() */ static void killat( int *atc, void **atfiles ) { while( *atc ) { atfile_t *atp; atp = (atfile_t *)(atfiles[--*atc]); fclose( atp->fp ); free( atp ); atfiles[*atc] = NULL; } return; } /***************************************************************** add_diskkwds() */ /* Build parser keyword list from disk table entries */ static void add_diskkwds( qualp_t qualset, const char *qname, qualp_t *disks ) { qualp_t qp; if( *disks == NULL ) { disktypep_t dp; size_t n = 0; for( dp = disktype; dp->name != NULL; dp++ ) ; n = (size_t) (dp - disktype); if( (n << MOU_V_DEVTYPE) & ~MOU_DEVTYPE ) abort(); /* MOU_DEVTYPE isn't wide enough for the max index */ *disks = (qualp_t)calloc( n+1, sizeof( qual_t ) ); if( *disks == NULL ) { perror( "malloc" ); exit( EXIT_FAILURE ); } /* Do NOT sort 1st element (default, must have search seq 1) * or last - list terminator. */ qsort( disktype+1, n - 1, sizeof( disktype_t ), disktypecmp ); for( qp = *disks, dp = disktype; dp->name != NULL; dp++, qp++ ) { qp->name = dp->name; qp->set = ((options_t)(dp-disktype)) << MOU_V_DEVTYPE; qp->clear = MOU_DEVTYPE; qp->qtype = NOVAL; if( dp != disktype ) qp->helpstr = ""; } qp->set |= OPT_NOSORT; /* Mark table as non-sortable by help */ } for( qp = qualset; qp->name != NULL; qp++ ) if( !strcmp( qp->name, qname ) ) break; if( qp->name == NULL ) abort(); qp->arg = *disks; return; } /***************************************************************** disktypecmp() */ static int disktypecmp( const void *da, const void *db ) { return strcmp( ((disktypep_t )da)->name, ((disktypep_t )db)->name ); } /***************************************************************** cmdsplit() */ /* cmdsplit: break a command line into its components */ static vmscond_t cmdsplit( char *str ) { vmscond_t sts; int argc = 0, qualc = 0; char **argv = NULL, **qualv = NULL; char *sp = str; char q = qstyle_c; argv = (char **) calloc( 1, sizeof( char * ) ); qualv = (char **) calloc( 1, sizeof( char * ) ); if( argv == NULL || qualv == NULL ) { free( argv ); free( qualv ); return SS$_INSFMEM; } sts = SS$_NORMAL; while( *sp != '\0' ) { while( *sp == ' ' ) sp++; if( *sp == '\0' ) break; if( *sp == q ) { /* Start of qualifier */ *sp++ = '\0'; /* Terminate previous word */ if (*sp == q) { /* qq = end of qualifiers */ sp++; q = '\0'; continue; } if( $FAILS(sts = addarg( &qualc, (void ***)&qualv, (void *)sp)) ) break; } else { /* New argument */ int qt; if( $FAILS(sts = addarg( &argc, (void ***)&argv, (void *)sp)) ) break; if( (qt = *sp) == '"' || (qt = *sp) == '\'' ) { ++argv[argc-1]; for( ++sp; *sp && (*sp != qt || sp[1] == qt); sp++ ) { if( *sp == qt ) /* Interior "" => " */ memmove( sp, sp+1, strlen(sp+1)+1 ); } if( *sp == qt ) { /* Ending " of string */ *sp++ = '\0'; if( *sp && *sp != ' ' ) { /* Something following */ sts = printmsg( ODS2_UNTERMSTR, 0 ); break; } } else { sts = printmsg( ODS2_UNTERMSTR, 0 ); break; } continue; } /* Quoted string */ } /* Find end of atom */ while( !(*sp == '\0' || *sp == ' ' || *sp == q) ) sp++; if (*sp == '\0') break; if( *sp == ' ' ) *sp++ = '\0'; } if( $SUCCESSFUL(sts) && argc > 0 ) sts = cmdexecute( argc, argv, qualc, qualv ); free( argv ); free( qualv ); return sts; } /***************************************************************** addarg() */ /* Add an item to a dynamic list of things, e.g. argv, qualv,... * Updates count. */ static vmscond_t addarg( int *argc, void ***argv, void *arg ) { void **list; list = (void **) realloc( *argv, ((size_t)*argc + 2) * sizeof( void * ) ); if( list == NULL ) return SS$_INSFMEM; list[*argc] = arg; list[++*argc] = NULL; *argv = list; return SS$_NORMAL; } /*************************************************************** cmdexecute() */ /* cmdexecute: identify and execute a command */ static vmscond_t cmdexecute( int argc, char **argv, int qualc, char **qualv ) { char *ptr; CMDSETp_t cmd, cp = NULL; size_t cmdsiz; int minpars, maxpars; paramp_t p; vmscond_t sts; ptr = argv[0]; while (*ptr != '\0') { *ptr = tolower(*ptr); ptr++; } ptr = argv[0]; cmdsiz = strlen(ptr); for( cmd = maincmds; cmd->name != NULL; cmd++ ) { if( cmdsiz <= strlen( cmd->name ) && keycomp( ptr, cmd->name ) ) { if( cmd->uniq ) { if( cmd->uniq > 0 && (int) cmdsiz >= cmd->uniq ) { cp = cmd; /* Unique in n */ break; } if( cmd->uniq < 0 && cmdsiz < (size_t) abs( cmd->uniq ) ) cp = cmd; /* Requires n even if fewer unique */ } if( cp != NULL ) { return printmsg( ODS2_AMBIGUOUS, 0, "command", ptr ); } cp = cmd; } } if( cp == NULL ) { return printmsg( ODS2_UNKNOWN, 0, "command", ptr ); } cmd = cp; if( cmd->proc == NULL ) return ODS2_EXIT; minpars = maxpars = 1; for( p = cmd->params; p && p->name != NULL; p++ ) { if( ((unsigned)maxpars) != ~0u ) maxpars++; if( p->flags & REQ ) minpars++; if( p->flags & NOLIM ) maxpars = ~0u; } if (argc < minpars || (((unsigned)argc) != ~0u && ((unsigned)argc) > (unsigned)maxpars) ) { return printmsg( (argc < minpars? ODS2_TOOFEW: ODS2_TOOMANY), 0, "parameters" ); } sts = (*cmd->proc) (argc, argv, qualc, qualv); cache_flush(); return sts; } /*********************************************************** doecho() */ static DECL_CMD(echo) { UNUSED(argc); UNUSED(qualc); UNUSED(qualv); while( argv[1] ) { printf( "%s", argv++[1] ); if( argv[1] ) putchar( ' ' ); } putchar( '\n' ); fflush( stdout ); return SS$_NORMAL; } /*********************************************************** parselist() */ vmscond_t parselist( int *nret, char ***items, size_t min, char *arg ) { size_t n = 0, i; char **list = NULL; if( nret != NULL ) *nret = 0; *items = NULL; while( arg != NULL && *arg != '\0' ) { char **nl; while( *arg == ' ' || *arg == '\t' ) arg++; if( *arg == '\0' ) break; nl = (char **) realloc( list, (n < min? min +1: n +2) * sizeof( char * ) ); if( nl == NULL ) { free( list ); return printmsg( SS$_INSFMEM, 0 ); } list = nl; list[n++] = arg; while( !(*arg == ',' || *arg == '\0') ) arg++; if( *arg == '\0' ) break; *arg++ = '\0'; } if( list == NULL ) { n = 0; list = (char **) malloc( (min + 1) * sizeof( char * ) ); if( list == NULL ) { return printmsg( SS$_INSFMEM, 0 ); } } for( i = n; i < min; i++ ) list[i] = NULL; #ifdef _MSC_VER /* NOT a buffer overflow */ #pragma warning( push ) #pragma warning( disable: 6386 ) #endif list[i] = NULL; #ifdef _MSC_VER #pragma warning( pop ) #endif *items = list; if( nret != NULL ) *nret = (int)n; return SS$_NORMAL; } /******************************************************************* checkquals() */ /* checkquals: Given valid qualifier definitions, process qualifer * list from a command left to right. Also handles parameters and * qualifier values (/Qual=value). May recurse for keyword values. * May not modify caller's arglist as it's reused (e.g. with saved * default qualifier lists.) */ vmscond_t checkquals( options_t *result, options_t defval, qualp_t qualset, int qualc, char **qualv ) { vmscond_t sts; int i; const char *type; #ifdef _WIN32 /* Code analysis thinks qualset can be NULL, resulting in a false positive. */ if( qualset == NULL ) abort(); #endif *result = defval; type = (qualc < 0)? "parameter": "qualifier"; qualc = abs( qualc ); sts = SS$_NORMAL; for( i = 0; i < qualc; i++ ) { char *qc; static char *topv = NULL; static unsigned level = 0; do { char *qv, *nvp; qualp_t qs, qp = NULL; int qtype; size_t len; if( level++ ) { qc = qualv[i]; } else { len = strlen( qualv[i] ) + 1; if( (qc = malloc( len )) == NULL ) { --level; topv = NULL; return printmsg( SS$_INSFMEM, 0 ); } memcpy( qc, qualv[i], len ); topv = qualv[i]; } if( (qv = strchr( qc, '=' )) == NULL ) qv = strchr( qc, ':' ); if( qv != NULL ) *qv++ = '\0'; for( qs = qualset; qs->name != NULL; qs++) { int matched; if( (matched = keycomp( qc, qs->name ))!= 0 ) { if( matched == (int)strlen( qs->name ) ) { qp = qs; break; } if( qp != NULL ) { sts = printmsg( ODS2_AMBIGUOUS, 0, type, qc ); break; } qp = qs; } } if( $FAILED(sts) ) break; if( qp == NULL ) { sts = printmsg( ODS2_UNKNOWN, 0, type, qc ); break; } *result = (*result & ~qp->clear) | qp->set; qtype = qp->qtype; if( qv == NULL || *qv == '\0' ) { if( qtype > 0 ) { sts = printmsg( ODS2_VALUEREQ, 0, type, qp->name ); } break; } if( qtype == NOVAL ) { sts = printmsg( ODS2_NOVALUE, 0, type, qp->name ); break; } qtype = abs( qtype ); if( qtype == STRVAL ) { *((char **)qp->arg) = topv + (qv - qc); break; } if( *qv == '(' ) { qv++; nvp = strchr( qv, ')' ); if( nvp == NULL ) { sts = printmsg( ODS2_NOPAREN, 0, type, qc ); break; } *nvp = '\0'; } if( qtype == PROT ) { int err = FALSE; uint32_t prot = 0; unsigned class = 0; while( *qv != '\0' && !err ) { nvp = strchr( qv, ':' ); if( nvp == NULL ) { nvp = qv + strlen( qv ); } else { *nvp++ = '\0'; } if( keycomp(qv, "SYSTEM" ) ) class = (prot$m_system << 16) | prot$v_system; else if( keycomp(qv, "OWNER" ) ) class = (prot$m_owner << 16) | prot$v_owner; else if( keycomp( qv, "GROUP" ) ) class = (prot$m_group << 16) | prot$v_group; else if( keycomp( qv, "WORLD" ) ) class = (prot$m_world << 16) | prot$v_world; else { err = TRUE; break; } prot |= class & 0xFFFF0000; qv = nvp; nvp = strchr( qv, ',' ); if( nvp == NULL ) nvp = qv + strlen( qv ); else *nvp++ = '\0'; while( *qv != '\0' && !err ) { switch( toupper(*qv++) ) { case 'R': prot |= prot$m_noread << (uint16_t)class; break; case 'W': prot |= prot$m_nowrite << (uint16_t)class; break; case 'E': prot |= prot$m_noexe << (uint16_t)class; break; case 'D': prot |= prot$m_nodel << (uint16_t)class; break; default: err = TRUE; break; } } qv = nvp; } if( err || !(prot & 0xFFFF0000) ) { sts = printmsg( ODS2_BADPRO, 0 ); break; } *((uint32_t *)qp->arg) = ( (prot & 0xFFFF0000) | (uint16_t)((prot >> 16) & ~prot) ); break; } if( qtype == UIC ) { uint16_t grp, mem; int n; if( #ifdef _WIN32 sscanf_s #else sscanf #endif (qv, " [%ho,%ho]%n", &grp, &mem, &n ) < 2 || (size_t)n != strlen(qv) ) { sts = printmsg( ODS2_BADUIC, 0 ); break; } *((uint32_t*)qp->arg) = (grp << 16) | mem; break; } do { while( *qv == ' ' ) qv++; nvp = strchr( qv, ',' ); if( nvp != NULL ) *nvp++ = '\0'; switch( qtype ) { case DECVAL: { unsigned long int val; char *ep; errno = 0; val = strtoul( qv, &ep, 10 ); if( !*qv || *ep || errno != 0 ) { sts = printmsg( ODS2_INVNUMBER, 0, qv ); break; } *((uint32_t *)qp->arg) = (uint32_t) val; break; } case KEYVAL: case KEYCOL: sts = checkquals( result, *result, (qualp_t )qp->arg, -1, &qv ); break; default: abort(); } if( $FAILED(sts) ) break; qv = nvp; } while( qv != NULL ); } while( 0 ); if( !--level ) { free( qc ); topv = NULL; } if( $FAILED(sts) ) return sts; } return sts; } /******************************************************************* keycomp() */ /* keycomp: routine to compare parameter to a keyword - case insensitive */ int keycomp(const char *param, const char *keywrd) { const char *p; for( p = param; *p != '\0'; ) { if( tolower(*p++) != tolower(*keywrd++) ) return 0; } return (int) (p - param); } /*********************************************************** getcmd() */ #ifdef VMS static char *getcmd( char *inp, size_t max, char *prompt ) { struct dsc_descriptor prompt_d = { strlen(prompt), DSC$K_DTYPE_T, DSC$K_CLASS_S, prompt }; struct dsc_descriptor input_d = { max -1, DSC$K_DTYPE_T, DSC$K_CLASS_S, inp }; vmscond_t status; char *retstat; static unsigned long key_table_id = 0; static unsigned long keyboard_id = 0; if (key_table_id == 0) { if( $SUCCESSFUL(status = smg$create_key_table( &key_table_id )) ) status = smg$create_virtual_keyboard( &keyboard_id ); if( $FAILED(status) ) return (NULL); } status = smg$read_composed_line( &keyboard_id, &key_table_id, &input_d, &prompt_d, &input_d, 0, 0, 0, 0, 0, 0, 0 ); if( $MATCHCOND(status, SMG$_EOF) ) retstat = NULL; else { inp[input_d.dsc_w_length] = '\0'; retstat = inp; } return(retstat); } #endif /* VMS */ /*********************************************************** prvmstime() */ vmscond_t prvmstime( FILE *of, VMSTIME vtime, const char *sfx ) { vmscond_t sts = 0; char tim[24] = { "" }; static const VMSTIME nil; struct dsc_descriptor timdsc; memset( &timdsc, 0, sizeof( timdsc ) ); if( memcmp( vtime, nil, sizeof(nil) ) ) { timdsc.dsc_w_length = 23; timdsc.dsc_a_pointer = tim; if( $FAILS( sts = sys_asctim( 0, &timdsc, vtime, 0 ) ) ) { printmsg( ODS2_VMSTIME, MSG_TOFILE, of ); sts = printmsg( sts, MSG_CONTINUE|MSG_TOFILE, of, ODS2_VMSTIME ); } tim[23] = '\0'; fprintf( of, " %s", tim ); } else { fprintf( of, " %-23s", " " ); sts = SS$_NORMAL; } if( sfx != NULL ) fprintf( of, "%s", sfx ); return sts; } /*********************************************************** pwrap() */ void pwrap( FILE *of, int *pos, const char *fmt, ... ) { char pbuf[200], *p, *q; va_list ap; va_start(ap, fmt ); vsnprintf( pbuf, sizeof(pbuf), fmt, ap ); va_end(ap); for( p = pbuf; *p; ) { int len; int eol = 0; q = strchr( p, '\n' ); if( q != NULL ) { *q++ = '\0'; eol = 1; len = (int)strlen(p); } else { len = (int)strlen(p); q = p + len; } if( *pos + len > 80 ) { static const char wrap[] = " "; fprintf( of, "\n%s", wrap ); *pos = sizeof(wrap) -1; if( p[0] == ',' && p[1] == ' ' ) p += 2; } *pos += (int)strlen(p); fprintf( of, "%s%s", p, eol? "\n":"" ); if( eol ) *pos = 0; p = q; } }