Files
open-simh.simtools/extracters/ods2/ods2.c
Timothe Litt 66e00b9900 Backlog of work since 2016
Too much to list all, but includes (in no particular order):
 - Cleanup for 64-bit builds, MSVC warnings.
 - Structured help
 - Help file compiler.
 - Supports volsets, writes/create work.
 - Support for I18n in messages, help.
 - Makefiles.
 - Initialize volume/volset
 - Command line editing/history

Builds and works on Linux and Windows (VS).
Not recently built or tested on other platforms, but
not intentinonally broken.
2022-10-10 11:00:20 -04:00

1364 lines
42 KiB
C

/* 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 <smgdef.h> /* For VMS version of getcmd() */
#include <smgmsg.h> /* SMG$_EOF */
#include <smg$routines.h>
#else
#include <locale.h>
#endif /* VMS */
#ifndef _WIN32
#include <sys/stat.h>
#endif
#define DEBUGx on
#include <signal.h>
#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 <histedit.h>
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", " <Not recorded>" );
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;
}
}