[mod.computers.vax] Parsing VMS filenames

rpf%OVRO.Caltech.Edu@HAMLET.CALTECH.EDU (Ray Finch) (11/03/86)

     I am writing a user interface program for radio telescopes and
am trying to locate a piece of code to parse VMS file names.  I
would prefer C but we also have Fortran and Pascal compilers.  I'm
trying to avoid having figuring out the call to SYS$PARSE and would
rather not have to open the file at this point in the program.

     Thanks in advance for any helpful responses.

			Ray Finch
			Owens Valley Radio Observatory
			P.O. Box 387
			Big Pine, CA.  93513
			(619)938-2828

LEICHTER-JERRY@YALE.ARPA (11/06/86)

         I am writing a user interface program for radio telescopes and
    am trying to locate a piece of code to parse VMS file names.  I
    would prefer C but we also have Fortran and Pascal compilers.  I'm
    trying to avoid having figuring out the call to SYS$PARSE and would
    rather not have to open the file at this point in the program.
Try the following (I didn't write it but I do use it).
							-- Jerry
---------------------------------Cut here-------------------------------------
/*)LIBRARY
*/

/*

fparse.c - fparse routine to emulate f$parse in DCL command files

Synposis:

	char *fparse( file_name, default_name, related_name, field)
	    char *file_name, default_name, related_name, field;

Description:

	All strings are null-terminated.  Only the first one or two characters
	are checked in field names. Parameters can be omitted by passing NULL.

	Fields are:

	    "node"		node name
	    "device"		device name
	    "directory"		directory name
	    "name"		file name
	    "type"		file type
	    "version"		file version number

	If the field parameter is null, all fields are expanded, except that
	the node name is included only if it appears in the file_name,
	default_name or related_name.  Note that field parameters
	must be given in lower-case.

	Within each field, the expanded name is taken from the file_name,
	default_name and related_name, in that order.

	The value returned is the address of the null-terminated file name;
	fparse calls malloc to reserve space for the string.

	The string "" is returned on either an RMS parse error, or
	an erroroneous field parameter name.  The RMS status error
	is not available.  Only one field may be given.

Example:


	To parse a command line LINK/EXE=exefile objfile, the default extension
	for "objfile" is .OBJ.  The default file name for "exefile" is the file
	name of "objfile", and the default extension is .EXE.

	Say that the char * variable "objfile" points to the object file
	name from the command line, and the char * "exefile" points to the
	exe file from the command line.  Then to expand these into file
	names for calls to open() or fopen():

		objfile = fparse( objfile, ".OBJ", NULL, NULL);
		exefile = fparse( exefile, ".EXE", objfile, NULL);

	To find only the directory of the object file:

		dir = fparse( objfile, ".OBJ", NULL, "directory");

	The field name could also be abbreviated:

		dir = fparse( objfile, ".OBJ", NULL, "di");

*/

#include <stdio.h>
#include <rms.h>
#include <ssdef.h>

char *fparse( file_name, default_name, related_name, field)
    char *file_name, *default_name, *related_name, *field;
{
    struct FAB ff_file_fab;
    struct NAM fn_file_nam, rn_related_nam, *nam;
    char *expanded_name, *eptr;
    int expanded_length, rms_status;
    char expand_buffer[ NAM$C_MAXRSS];

/* initialize all the blocks for RMS */

    ff_file_fab = cc$rms_fab;
    ff_file_fab.fab$l_nam = &fn_file_nam;
    ff_file_fab.fab$l_fna = file_name;
    ff_file_fab.fab$b_fns = (file_name == NULL) ? 0 : strlen( file_name);
    ff_file_fab.fab$l_dna = default_name;
    ff_file_fab.fab$b_dns = (default_name == NULL) ? 0 : strlen( default_name);

    fn_file_nam = cc$rms_nam;
    nam = &fn_file_nam;
    nam->nam$l_esa = expand_buffer;
    nam->nam$b_ess = NAM$C_MAXRSS;
    nam->nam$l_rlf = &rn_related_nam;

    rn_related_nam = cc$rms_nam;
    rn_related_nam.nam$l_rsa = related_name;
    rn_related_nam.nam$b_rsl =
      (related_name == NULL) ? 0 : strlen( related_name);

/* call SYS$PARSE to parse the file name */

    rms_status = sys$parse( &ff_file_fab);
    if (rms_status != RMS$_NORMAL)
	{  /* error in parse, so return empty string */
	expanded_name = malloc(1);
	expanded_name[0] = '\0';
	return( expanded_name);
	}

/* construct the expanded file name */

    if (field == NULL || field[0] == '\0')
	{  /* caller wants all fields */
	expanded_length = nam->nam$b_esl;
	eptr = expand_buffer;
	}
    else
	{  /* caller wants just one field */
	switch (field[0])
	    {
	    case 'n':		/* node or name */
		if (field[1] == 'o')
		    {		/* node */
		    expanded_length = nam->nam$b_node;
		    eptr = nam->nam$l_node;
		    }
		else
		    {		/* name */
		    expanded_length = nam->nam$b_name;
		    eptr = nam->nam$l_name;
		    }
		break;
	    case 'd':		/* device or directory */
		if (field[1] == 'e')
		    {		/* device */
		    expanded_length = nam->nam$b_dev;
		    eptr = nam->nam$l_dev;
		    }
		else
		    {		/* directory */
		    expanded_length = nam->nam$b_dir;
		    eptr = nam->nam$l_dir;
		    }
		break;
	    case 't':		/* type */
		expanded_length = nam->nam$b_type;
		eptr = nam->nam$l_type;
		break;
	    case 'v':		/* version */
		expanded_length = nam->nam$b_ver;
		eptr = nam->nam$l_ver;
		break;
	    default:
		expanded_length = 0;
		break;
	    }
	}
    expanded_name = malloc( expanded_length + 1);
    strncpy( expanded_name, eptr, expanded_length);
    expanded_name[ expanded_length] = '\0';
    return( expanded_name);
}
-------

McGuire_Ed@GRINNELL.MAILNET (11/06/86)

You're not going to find my answer much help, because I can't provide a piece
of code to parse VMS file names, nor have I used SYS$PARSE.  But I do feel
motivated to provide some advice (it's free :-)

Don't parse it yourself. Use the system services instead.  What happens to your
nice application program if DEC extends the syntax of filenames like they did
in V4.0 (with long name and type) and V4.4 (is that right?) with the addition
of the hyphen?  Any user-written  parsing code will probably trap an apparent
error in the file name instead of handling the additional syntax!

LEICHTER-JERRY@YALE.ARPA (11/08/86)

In my previous posting on this subject, I included source code for a function
called fparse() that did more or less what the DCL lexical function F$PARSE()
does.  Staring at the code inspired me to fix it up and extend it a bit.
Below is a better - slightly incompatible! - version.  (The test program
calls a function called vms_etext, which is just a C interface to SYS$GETMSG,
and is omitted.)
							-- Jerry

----------------------------------Cut here------------------------------------
/*
 *		F P A R S E
 *
 * C-callable equivalent of the DCL F$PARSE lexical function.
 */

/*)LIBRARY
*/

#ifdef DOCUMENTATION

title	fparse	Parse VMS file specification a la DCL's F$PARSE
index		Parse VMS file specification a la DCL's F$PARSE

synopsis

	char *
	fparse(file_name,default_name,related_name,fields,options)
	char *file_name;			/* Optional	*/
	char *default_name;			/* Optional	*/
	char *related_name;			/* Optional	*/
	char *fields;				/* Optional	*/
	char *options;				/* Optional	*/

	extern noshare int fparse_dev;
	extern noshare int fparse_sdc;
	extern noshare int fparse_sts;
	extern noshare int fparse_stv;
	extern noshare int fparse_fnb;

description

	fparse() provides a simple interface to the SYS$PARSE() service.  It's
	interface is based on the DCL F$PARSE() lexical function.

	All strings are null-terminated.  Parameters can be omitted by passing
	NULL; trailing parameters may be omitted completely.

	Fields are:

	    "node"		node name
	    "device"		device name
	    "directory"		directory name
	    "name"		file name
	    "type"		file type
	    "version"		file version number

	Options to control the parse:

	    "noconceal"		Don't conceal device name
	    "pwd"		Leave actual DECnet password in place
	    "synchk"		No I/O - check syntax only
	    "ofp"		Do an output file parse

	Any number of field names and options may be specified in any order,
	separated by commas.  Only the first one or two characters of field
	and option names are checked (enough to identify the name uniquely).
	All field and options names must be given in lower case only.

	If the fields parameter is NULL or omitted, all fields are returned,
	except that the node name is included only if it appears explicitly.
	(This is the underlying behavior of SYS$PARSE.)

	The value returned is the address of the null-terminated expanded
	specification.  fparse calls malloc() to reserve space for the string;
	you should free it with free() when you are done with it.

	NULL is returned on either an RMS parse error, or an erroneous field
	parameter name or option.

	The RMS status is is available in symbol fparse_sts; for a bad field
	or option, it will contain SS$_BADPARAM.  If malloc() returns NULL,
	so does fparse(), but in this case fparse_sts will contain contain
	RMS$_NORMAL.  Other values returned as a result of the parse are
	available as follows:

	FAB$L_DEV	fparse_dev
	FAB$L_SDC	fparse_sdc
	FAB$L_STS	fparse_sts
	FAB$L_STV	fparse_stv
	NAM$L_FNB	fparse_fnb

	Wild-card context for the fab used is always discarded by fparse();
	it cannot be used to set up the control blocks for a call to $SEARCH.
	Certain additional RMS fields - FAB$W_DID, for example - are not
	available.

examples

	In determining the actual file specifications to use on a command
	line such as LINK/EXE=exefile objfile, the default type for "objfile"
	is .OBJ.  The default file name for "exefile" is the file name of
	"objfile", and the default type is .EXE.

	If the (char *) variable "objfile" points to the object file name from
	the command line, and the (char *) "exefile" points to the exe file
	from the command line.  Then to expand these into file names for calls
	to open() or fopen():

		objfile = fparse(objfile,".OBJ");
		exefile = fparse(exefile,".EXE",objfile,NULL,"ofp");

	(The "ofp" option was specified to avoid copying anything but the
	name and type from the object file - the exe file should go, for
	example, to the current default directory, not the object file's
	directory.)

	To find only the device and directory of the object file:

		dir = fparse(objfile,".OBJ",NULL,"device,directory");

	This could as well have been written:

		dir = fparse(objfile,".OBJ",NULL,"de,di");

bugs

	Since the various fparse_... fields are statically allocated, the
	code is not reentrant.

authors

	Bob Messenger; re-written by Jerry Leichter

#endif

/*
 * Revision History
 *	1.0 ??-???-85	 BM	Invention
 *	2.0  6-Nov-86	JSL	Extensive re-write; added options, ability
 *				to specify multiple fields at once.  Clean
 *				up fab context before returning.  Now returns
 *				NULL, not an empty string, on errors, and
 *				makes the various RMS values accessible.
 */

#include stdio
#include rms
#include ssdef
#include varargs

#define EOS	'\0'

static char *copyn();
extern char *malloc();

noshare int fparse_dev;
noshare int fparse_sdc;
noshare int fparse_sts;
noshare int fparse_stv;
noshare int fparse_fnb;

char *
fparse(va_alist)
va_dcl
{	char		*file;
	char		*defaultn;
	char		*related;
	char		*fields;
	char		*options;
	struct FAB	fab;
	struct NAM	nam;
	struct NAM 	rnam;
	char		*result;
	int		len;
	char		expand_buf[NAM$C_MAXRSS];
	int		nargs;
	va_list		va;
	int		fbits;
#define NODE	 1
#define DEV	 2
#define DIR	 4
#define	NAME	 8
#define TYPE	16
#define	VER	32
#define ALL	(NODE|DEV|DIR|NAME|TYPE|VER)

	/*
	 * Pick up the arguments, forcing all missing ones to NULL
	 */
	va_count(nargs);
	va_start(va);
	file		= (nargs > 0) ? va_arg(va,char *) : NULL;
	defaultn	= (nargs > 1) ? va_arg(va,char *) : NULL;
	related		= (nargs > 2) ? va_arg(va,char *) : NULL;
	fields		= (nargs > 3) ? va_arg(va,char *) : NULL;
	options		= (nargs > 4) ? va_arg(va,char *) : NULL;
	va_end(va);

	/*
	 * Initialize all the blocks for RMS
	 */
	fab = cc$rms_fab;
	fab.fab$l_nam = &nam;
	fab.fab$l_fna = file;
	fab.fab$b_fns = (file == NULL) ? 0 : strlen(file);
	fab.fab$l_dna = defaultn;
	fab.fab$b_dns = (defaultn == NULL) ? 0 : strlen(defaultn);

	nam = cc$rms_nam;
	nam.nam$l_esa = expand_buf;
	nam.nam$b_ess = NAM$C_MAXRSS;
	nam.nam$l_rlf = &rnam;

	rnam = cc$rms_nam;
	rnam.nam$l_rsa = related;
	rnam.nam$b_rsl = (related == NULL) ? 0 : strlen(related);

	fparse_sts = SS$_BADPARAM;		/* Assume failure	*/
	/*
	 * Figure out what fields the user wants returned
	 */
	if (fields == NULL || *fields == EOS)
		fbits = ALL;
	else
	{	fbits = 0;
		while (*fields != EOS)
		{	switch (*fields)
			{
		case 'n':				/* node or name	*/
				if (*++fields == 'o')	/* node		*/
					fbits |= NODE;
				else if (*fields == 'a')/* name		*/
					fbits |= NAME;
				else	return(NULL);	/* n?		*/
				break;
		case 'd':			/* device or directory	*/
				if (*++fields == 'e')	/* device	*/
					fbits |= DEV;
				else if (*fields == 'i')/* directory	*/
					fbits |= DIR;
				else	return(NULL);	/* d?		*/
				break;
		case 't':				/* type		*/
				fbits |= TYPE;
				break;
		case 'v':				/* version	*/
				fbits |= VER;
				break;
		default:				/* Unknown	*/
				return(NULL);
			}
			while (*fields != ',' && *fields != EOS)
				fields++;
			if (*fields != EOS)
				fields++;
		}
	}

	/*
	 * Scan options and set the appropriate RMS bits.
	 */
	if (options != NULL)
	{	while (*options != EOS)
		{	switch(*options)
			{
		case 'n':				/* noconceal	*/
				nam.nam$b_nop |= NAM$M_NOCONCEAL;
				break;

		case 'o':				/* ofp		*/
				fab.fab$l_fop |= FAB$M_OFP;
				break;

		case 'p':				/* pwd		*/
				nam.nam$b_nop |= NAM$M_PWD;
				break;

		case 's':				/* synchk	*/
				nam.nam$b_nop |= NAM$M_SYNCHK;
				break;

		default:				/* unknown	*/
				return(NULL);
			}
			while (*options != ',' && *options != EOS)
				options++;
			if (*options != EOS)
				options++;
		}
	}

	/*
	 * call SYS$PARSE to parse the file name
	 */
	fparse_sts = SYS$PARSE(&fab);
	fparse_dev = fab.fab$l_dev;
	fparse_sdc = fab.fab$l_sdc;
	fparse_stv = fab.fab$l_stv;
	fparse_fnb = nam.nam$l_fnb;
	if (fparse_sts != RMS$_NORMAL)
		return(NULL);

	/*
	 * Calculate the total length of the string we will be returning.
	 * In the process, zero out the lengths of the fields we are not
	 * returning to simplify the later copy code.
	 */
	len = 0;
	if (fbits & NODE)
		len += nam.nam$b_node;
	else	nam.nam$b_node = 0;

	if (fbits & DEV)
		len += nam.nam$b_dev;
	else	nam.nam$b_dev = 0;

	if (fbits & DIR)
		len += nam.nam$b_dir;
	else	nam.nam$b_dir = 0;

	if (fbits & NAME)
		len += nam.nam$b_name;
	else	nam.nam$b_name = 0;

	if (fbits & TYPE)
		len += nam.nam$b_type;
	else	nam.nam$b_type = 0;

	if (fbits & VER)
		len += nam.nam$b_ver;
	else	nam.nam$b_ver = 0;

	/*
	 * Construct the expanded file name
	 */
	if ((result = malloc(len + 1)) != NULL)
	{	char	*p;

		p = copyn(result,nam.nam$l_node,nam.nam$b_node);
		p = copyn(p,nam.nam$l_dev,nam.nam$b_dev);
		p = copyn(p,nam.nam$l_dir,nam.nam$b_dir);
		p = copyn(p,nam.nam$l_name,nam.nam$b_name);
		p = copyn(p,nam.nam$l_type,nam.nam$b_type);
		p = copyn(p,nam.nam$l_ver,nam.nam$b_ver);
		*p++ = EOS;
	}

	/*
	 * Release any space reserved by RMS for wild-card expansion
	 */
	nam.nam$b_nop = NAM$M_SYNCHK;
	fab.fab$b_dns = 0;
	nam.nam$l_rlf = NULL;
	SYS$PARSE(&fab);

	return(result);
}

/*
 * copyn n >= 0 characters from in to out; return (out + n), i.e., the next
 * free character after the characters copied.
 */
static char *
copyn(out,in,n)
char	*out;
char	*in;
int	n;
{
	while (n-- > 0)
		*out++ = *in++;
	return(out);
}

#ifdef TESTING
/*
 * Simple test program
 */
main()
{	char a[200],b[200],c[200],d[200],e[200];
	char *r;

	for (;;)
	{	printf("file name: ");
		gets(a);
		printf("default name: ");
		gets(b);
		printf("related name: ");
		gets(c);
		printf("fields: ");
		gets(d);
		printf("options: ");
		gets(e);
		r = fparse(a,b,c,d,e);
		if (r == NULL)
			printf("[Null]\n");
		else
		{	puts(r);
			free(r);
		}
		printf("sts %X, stv %X, dev %X, sdc %X, fnb %X\n",
			fparse_sts, fparse_stv, fparse_dev, fparse_sdc, 
			fparse_fnb);
		if (fparse_sts != RMS$_NORMAL)
		{	printf("sts: %s\n",vms_etext(fparse_sts));
			if (fparse_stv != 0)
				printf("stv: %s\n",vms_etext(fparse_stv));
		}
	}
}
#endif
-------