From:	SMTP%"everhart@star.zko.dec.com"  7-AUG-1995 18:45:01.53
To:	EVERHART
CC:	
Subj:	Re: readdir on VMS - comp.os.vms #126539

Date: Mon, 7 Aug 1995 13:53:38 -0400
Message-Id: <95080713533756@star.zko.dec.com>
From: everhart@star.zko.dec.com (Glenn C. Everhart 603 881 1497)
To: everhart@gce.MV.COM
Subject: Re: readdir on VMS - comp.os.vms #126539
X-Vms-To: smtp%"everhart@gce.mv.com"

In article <405dtu$nsc@gap.cco.caltech.edu>, mathog@seqaxp.bio.caltech.edu writes:
In article <3vtivl$bob@holmes.ee.ubc.ca>, beng@canucks.ee.ubc.ca (Brian Eng (N.B.S)) writes:
>Hi, 
>I'm looking for the dir.[c,h] library for VMS which is
>similar to the UNIX version.  The function I need
>is the readdir function.  Does anyone know where
>I can find this??
>
Here's one that I got from Pat Rankin, modified from the original by Rich
Salz.  It works pretty well for me. It consists of two parts: dirent.h and
readdir.c. 

David Mathog
mathog@seqaxp.bio.caltech.edu
Manager, sequence analysis facility, biology division, Caltech 

***dirent.h*************************************
/*
**  Header file for VMS readdir() routines.
**  Written by Rich $alz, <rsalz@bbn.com> in August, 1990.
**  This code has no copyright.
**
**  Feb'95 --	eliminate reliance on <descrip.h>; safeguard multiple
**		inclusion; fully prototype the routines provided.	[pr]
*/
#ifndef DIRENT_H
#define DIRENT_H

    /* Data structure returned by READDIR(). */
struct dirent {
    char	d_name[100];		/* File name		*/
    int		vms_verscount;		/* Number of versions	*/
    short		vms_versions[64];	/* Version numbers	*/
};

    /* Handle returned by opendir(), used by the other routines.  You
     * are not supposed to care what's inside this structure. */
typedef struct _dirdesc {
    unsigned long	context;
    int			count;
    int			vms_wantversions;
    char		*pattern;
    struct dirent	entry;
				/* pattern string descriptor */
    struct { unsigned short len, mbz; char *adr; } pat;
} DIR;


#define rewinddir(dirp)		seekdir((dirp), 0L)


extern DIR		*opendir(const char *);
extern struct dirent	*readdir(DIR *);
extern long		telldir(DIR *);
extern void		seekdir(DIR *,long);
extern void		closedir(DIR *);
extern int		vmsreaddirversions(DIR *,int);

#endif	/*DIRENT_H*/
****readdir.c************************************
/*
**  VMS readdir() routines.
**  Written by Rich $alz, <rsalz@bbn.com> in August, 1990.
**  This code has no copyright.
**
**  Feb'95 --	reliance on <descrip.h> eliminated, here and in dirent.h;
**		support for Unix-style directory specifications implemented;
**		vmsreaddirversions() modified to return prior flag setting;
**		use ANSI headers <stdlib.h> and <string.h> plus VMS headers
**		<lib$routines.h> and <fscndef.h>; minor lint cleanup.	[pr]
**
**  Known problems:
**		garbage directory specifications aren't always diagnosed.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <lib$routines.h>
#include <fscndef.h>	/* $FILESCAN arguments */
#include <rmsdef.h>
#include "dirent.h"

    /* Uncomment the next line to get a test routine. */
/* #define TEST */

    /* Number of elements in vms_versions array */
#define VERSIZE(e)	(sizeof e->vms_versions / sizeof e->vms_versions[0])

    /* Simplified string descriptor */
struct dsc { unsigned short len, mbz; char *adr; };

    /* Parts of file specification used to specify a directory. */
#define FSCN_DIR (FSCN$M_NODE | FSCN$M_DEVICE | FSCN$M_ROOT | \
        FSCN$M_DIRECTORY | FSCN$M_NODE_PRIMARY | FSCN$M_NODE_ACS |\
        FSCN$M_NODE_SECONDARY)

extern unsigned long sys$filescan();

    /* Filename syntax conversion routine */
#ifdef __DECC	/* (really depends on run-time library, not compiler) */
#define cvt__to_vms decc$to_vms
#else
#define cvt__to_vms shell$to_vms
#endif
extern int	cvt__to_vms(char *,int (*)(char *,int),int,int);
static int	cvt_action(char *,int);

    /* Callback routine uses non re-entrant communication w/ caller. */
static char	nambuf[255+1];

/*
**  XXX$to_vms callback routine.  Note: old versions of shell$to_vms
**  didn't document the second argument, which indicates whether a
**  directory of foo/bar was converted into "[.foo]bar.dir" rather
**  than "[.foo.bar]" (such conversion is requested by passing non-zero
**  as XXX$to_vms's 4th argument; also undocumented in old versions).
*/
static int
cvt_action(char *name, int dir_flag___unused)
{
    nambuf[sizeof nambuf - 1] = '\0';
    (void)strncpy(nambuf, name, sizeof nambuf - 1);
    /* An even value tells xxx$to_vms that conversion should stop;
       that point is moot here since wildcard processing is disabled. */
    return 0;
}

/*
**  Open a directory, return a handle for later use.
*/
DIR *
opendir(name)
    const char		*name;
{
    DIR			*dd;
    struct dsc		name_dsc;
    int			len, retry = 0;
    unsigned long	flags;

    /* Validate the directory name argument.  ("[...]" is allowed!) */
    if (!name || !*name || strchr(name, '*') != 0
      || strchr(name, '%') != 0 || strchr(name, '?') != 0) {
	/* Name is required; wildcards are not allowed. */
	errno = ENOTDIR;
	return NULL;
    }
    nambuf[sizeof nambuf - 1] = '\0';	/* (strncpy doesn't guarantee it) */
    name_dsc.adr = strncpy(nambuf, name, sizeof nambuf - 1);
    name_dsc.mbz = 0;
    do {
	name_dsc.len = len = strlen(nambuf);
	/* Check which components of file specification seem to be present. */
	flags = ~0;	    /* (init so that we can ignore return status) */
	(void)sys$filescan(&name_dsc, (void *)0, &flags);
	if ((flags & ~FSCN_DIR) != 0 || strchr(nambuf, '/') != 0) {
	    /* Didn't supply nice directory name; check for Unix-style one. */
		/* (stupid shell$to_vms doesn't like trailing slash) */
	    if (len > 1 && nambuf[len - 1] == '/') nambuf[len - 1] = '\0';

	    if (++retry > 1 || cvt__to_vms(nambuf, cvt_action, 0, 0) != 1) {
		errno = ENOTDIR;
		return NULL;
	    } /* else:	cvt_action() has updated `nambuf'; `retry' is now 1 */
	} else retry = 0;  /* have something, possibly after retrying once */
    } while (retry);

    /* Get memory for the handle, and the pattern. */
    if ((dd = (DIR *)malloc(sizeof *dd)) == NULL) {
	errno = ENOMEM;
	return NULL;
    }
    dd->pattern = malloc((unsigned int)(strlen(nambuf) + sizeof "*.*"));
    if (dd->pattern == NULL) {
	free((void *)dd);
	errno = ENOMEM;
	return NULL;
    }

    /* Fill in the fields. */
    (void)strcat(strcpy(dd->pattern, nambuf), "*.*");
    dd->context = 0;
    dd->count = 0;
    dd->vms_wantversions = 0;
    dd->pat.adr = dd->pattern;
    dd->pat.len = strlen(dd->pattern);
    dd->pat.mbz = 0;
    (void)memset(&dd->entry, 0, sizeof dd->entry);

    return dd;
}


/*
**  Set the flag to indicate we want versions or not.  Return old setting.
*/
int
vmsreaddirversions(dd, flag)
    DIR		*dd;
    int		flag;
{
    int old_flag = dd->vms_wantversions;

    dd->vms_wantversions = flag;
    return old_flag;
}


/*
**  Free up an opened directory.
*/
void
closedir(dd)
    DIR		*dd;
{
    if (dd) {
	(void)lib$find_file_end(&dd->context);
	free(dd->pattern);
	free((void *)dd);
    }
}


/*
**  Collect all the version numbers for the current file.
*/
static void
collectversions(dd)
    DIR			*dd;
{
    struct dsc		pat;
    struct dsc		res;
    struct dirent	*e;
    char		*p;
    char		buff[sizeof dd->entry.d_name];
    int			i;
    char		*text;
    long		context;

    /* Convenient shorthand. */
    e = &dd->entry;

    /* Add the name plus version wildcard, replacing the "*.*" put on before */
    i = strlen(dd->pattern);
 /* assert( i > 3 ); */
    text = malloc((unsigned int)((i - 3) + strlen(e->d_name) + sizeof ";*"));
    if (text == NULL)
	return;
    (void)strcpy(text, dd->pattern);
    (void)strcat(strcpy(&text[i - 3], e->d_name), ";*");

    /* Set up the pattern and result descriptors. */
    pat.adr = text;
    pat.len = strlen(text);
    res.adr = buff;
    res.len = sizeof buff - 1;
    pat.mbz = res.mbz = 0;

    /* Read files, collecting versions. */
    for (context = 0; e->vms_verscount < VERSIZE(e); e->vms_verscount++) {
	if (lib$find_file(&pat, &res, &context) == RMS$_NMF || context == 0)
	    break;
	buff[sizeof buff - 1] = '\0';
	if (p = strchr(buff, ';'))
	    e->vms_versions[e->vms_verscount] = atoi(p + 1);
	else
	    e->vms_versions[e->vms_verscount] = -1;
    }
    if (e->vms_verscount < VERSIZE(e))
	e->vms_versions[e->vms_verscount] = -1;

    (void)lib$find_file_end(&context);
    free(text);
}


/*
**  Read the next entry from the directory.
*/
struct dirent *
readdir(dd)
    DIR			*dd;
{
    struct dsc		res;
    char		*p, *q;
    int			i;

    /* Set up result descriptor, and get next file. */
    res.adr = nambuf;
    res.len = sizeof nambuf - 1;
    res.mbz = 0;
    dd->count++;
    /* (ought to check for generic success/failure, not a specific value) */
    if (lib$find_file(&dd->pat, &res, &dd->context) == RMS$_NMF
     || dd->context == 0L)
	/* None left... */
	return NULL;

    /* Strip trailing blanks. */
    nambuf[sizeof nambuf - 1] = '\0';
    for (p = &nambuf[sizeof nambuf - 1]; p > nambuf && *--p == ' '; )
	*p = '\0';

    /* Skip any directory component and just copy the name. */
    p = nambuf;
    while ((q = strchr(p, ']')) != 0 || (q = strchr(p, '>')) != 0)
	p = q + 1;
    (void)strncpy(dd->entry.d_name, p, sizeof dd->entry.d_name - 1);
    dd->entry.d_name[sizeof dd->entry.d_name - 1] = '\0';

    /* Clobber the version. */
    if ((p = strchr(dd->entry.d_name, ';')) != 0)
	*p = '\0';

    dd->entry.vms_verscount = 0;
    dd->entry.vms_versions[0] = 0;
    if (dd->vms_wantversions)
	collectversions(dd);
    return &dd->entry;
}


/*
**  Return something that can be used in a seekdir later.
*/
long
telldir(dd)
    DIR		*dd;
{
    return dd->count;
}


/*
**  Return to a spot where we used to be.  Brute force.
*/
void
seekdir(dd, count)
    DIR		*dd;
    long	count;
{
    int		vms_wantversions;

    /* If we haven't done anything yet... */
    if (dd->count == 0)
	return;

    /* Remember some state, and clear it. */
    vms_wantversions = dd->vms_wantversions;
    dd->vms_wantversions = 0;
    (void)lib$find_file_end(&dd->context);
    dd->context = 0;

    /* The increment is in readdir(). */
    for (dd->count = 0; dd->count < count; )
	(void)readdir(dd);

    dd->vms_wantversions = vms_wantversions;
}


#ifdef	TEST
#include "dirent.h"

int
main()
{
    char		buff[256];
    DIR			*dd;
    struct dirent	*dp;
    int			i;
    int			j;

    for ( ; ; ) {
	printf("\n\nEnter dir:  ");
	(void)fflush(stdout);
	if (!gets(buff) || buff[0] == '\0')
	    break;
	if ((dd = opendir(buff)) == NULL) {
	    perror(buff);
	    continue;
	}

	/* Print the directory contents twice, the second time print
	 * the versions. */
	for (i = 0; i < 2; i++) {
	    while (dp = readdir(dd)) {
		printf("%s%s", i ? "\t" : "    ", dp->d_name);
		for (j = 0; j < dp->vms_verscount; j++)
		    printf("  %d", dp->vms_versions[j]);
		printf("\n");
	    }
	    rewinddir(dd);
	    vmsreaddirversions(dd, 1);
	}
	closedir(dd);
    }
    exit(0);
    /*NOTREACHED*/
    return 0;
}
#endif	/* TEST */

