[comp.os.vms] A readdir

rsalz@bbn.com (Rich Salz) (08/20/90)

I needed the "readdir" package for VMS.  Other implementations I've seen
all seemed to have lots of knowledge of the internals of the VMS directory
structure (eww, gross!) or didn't really handle file versions the way that
I needed them done.

The following code is in the public domain.  I hope it gets very widespread
use.  Enjoy.
	/r$

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix@uunet.uu.net if you want that tool.
# Contents:  README dirent.h vmsreaddir.c
# Wrapped by rsalz@litchi.bbn.com on Mon Aug 20 09:53:15 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive."'
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
  echo shar: Extracting \"'README'\" \(3306 characters\)
  sed "s/^X//" >'README' <<'END_OF_FILE'
X
XThis code implements the Berkeley Unix "readdir" package.  Readdir is an
Xabstract interface for reading entries out of directories, and getting the
Xnames of the files (and directories, etc.) contained therein.  Versions
Xexist for all Unix systems, MS-DOS, Amiga, and no doubt others as well.
X
XThis code was written by Rich $alz, <rsalz@bbn.com> in August, 1990, and
Xhas no copyright.
X
X    #include <dirent.h>
XThis header file must be included by any files that use any of the routines
Xdescribed here.  You must also #include <descrip.h> first.
X
X    DIR *
X    opendir(name)
X	char	*name;
XNAME is a filespec that names a directory.  It does not have to be in
Xstrict canonical form -- logicals such as "sys$login:" will be expanded.
XOnce the directory has been opened, any of the other routines described
Xhere can be used on the handle that is returned.
X
X
X    void
X    vmsreaddirversions(dd, flag)
X	DIR		*dd;
X	int		flag;
XBy default, READDIR() ignores file versions, and returns a file name once,
Xno matter how many versions it has.  This routine can be used to collect
Xthe version numbers in an that is returned with each file.  The FLAG
Xargument should be non-zero to collect version information, or zero to
Xignore it.  This routine can be called any number of times while the
Xdirectory is open.
X
X
X    void
X    closedir(dd)
X	DIR		*dd;
XOnce you are finished reading the contents of a directory, the CLOSEDIR()
Xroutine frees up all the storage associated with it, and invalidates the
Xhandle.
X
X
X    struct dirent *
X    readdir(dd)
X	DIR		*dd;
XThe READDIR() routine takes a handle returned by OPENDIR() and returns the
Xname of the next file in the directory, or a NULL pointer when there are
Xno more files.  The STRUCT DIRENT returned by this routine will have the
Xfollowing fields that may be accessed by user code:
X    d_name[]		A C character string with the file name
X    vms_verscount	The number of versions the file has
X    vms_versions[]	An array of the version numbers; the number -1
X			indicates a file-parsing error.
XNote that the fields starting with "vms_" are not part of the standard
XREADDIR() library available on other systems, so strictly portable code
Xshould take care to avoid using them except on VMS machines.
X
X    long
X    telldir(dd)
X	DIR		*dd;
XThis routine returns a "magic cookie" that can be used in a later
XSEEKDIR() call.  This allows you to remember one or more spots while
Xreading through a directory and return to them later.
X
X
X    void
X    seekdir(dd, pos)
X	DIR		*dd;
X	long		pos;
XThe SEEKDIR() routine takes the "cookie" returned by a previous call to
XTELLDIR() and sets the internal state so that the next READDIR() call will
Xreturn the file read just after the TELLDIR() call.
X
X    void
X    rewinddir(dd)
X	DIR		*dd;
XThe REWINDDIR() routine resets the internal state of the DD handle so that
XREADDIR() starts reading over from the beginning of the directory.
X
XHere is a sample routine showing how to use the package:
X    main()
X    {
X	DIR		*dd;
X	struct dirent	*dp;
X	int		i;
X
X	if ((dd = opendir("sys$login")) == NULL) {
X	    perror(buff);
X	    continue;
X	}
X	vmsreaddirversions(dd, 1);
X
X	while (dp = readdir(dd)) {
X	    printf("%s", dp->d_name);
X	    for (i = 0; i < dp->vms_verscount; i++)
X		printf("  %d", dp->vms_versions[i]);
X	    printf("\n");
X	}
X	closedir(dd);
X	exit(0);
X    }
END_OF_FILE
  if test 3306 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
  fi
  # end of 'README'
fi
if test -f 'dirent.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'dirent.h'\"
else
  echo shar: Extracting \"'dirent.h'\" \(915 characters\)
  sed "s/^X//" >'dirent.h' <<'END_OF_FILE'
X/*
X**  Header file for VMS readdir() routines.
X**  Written by Rich $alz, <rsalz@bbn.com> in August, 1990.
X**  This code has no copyright.
X**
X**  You must #include <descrip.h> before this file.
X*/
X
X    /* Data structure returned by READDIR(). */
Xstruct dirent {
X    char	d_name[100];		/* File name		*/
X    int		vms_verscount;		/* Number of versions	*/
X    int		vms_versions[20];	/* Version numbers	*/
X};
X
X    /* Handle returned by opendir(), used by the other routines.  You
X     * are not supposed to care what's inside this structure. */
Xtypedef struct _dirdesc {
X    long			context;
X    int				vms_wantversions;
X    char			*pattern;
X    struct dirent		entry;
X    struct dsc$descriptor_s	pat;
X} DIR;
X
X
X#define rewinddir(dirp)		seekdir((dirp), 0L)
X
X
Xextern DIR		*opendir();
Xextern struct dirent	*readdir();
Xextern long		telldir();
Xextern void		seekdir();
Xextern void		closedir();
Xextern void		vmsreaddirversions();
END_OF_FILE
  if test 915 -ne `wc -c <'dirent.h'`; then
    echo shar: \"'dirent.h'\" unpacked with wrong size!
  fi
  # end of 'dirent.h'
fi
if test -f 'vmsreaddir.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'vmsreaddir.c'\"
else
  echo shar: Extracting \"'vmsreaddir.c'\" \(5014 characters\)
  sed "s/^X//" >'vmsreaddir.c' <<'END_OF_FILE'
X/*
X**  VMS readdir() routines.
X**  Written by Rich $alz, <rsalz@bbn.com> in August, 1990.
X**  This code has no copyright.
X*/
X#include <stdio.h>
X#include <ctype.h>
X#include <errno.h>
X#include <descrip.h>
X#include <rmsdef.h>
X#include "dirent.h"
X
X    /* Uncomment the next line to get a test routine. */
X/*#define TEST*/
X
X    /* Number of elements in vms_versions array */
X#define VERSIZE(e)	(sizeof e->vms_versions / sizeof e->vms_versions[0])
X
X    /* Linked in later. */
Xextern char	*malloc();
Xextern char	*strrchr();
Xextern char	*strcpy();
X
X
X/*
X**  Open a directory, return a handle for later use.
X*/
XDIR *
Xopendir(name)
X    char	*name;
X{
X    DIR		*dd;
X
X    /* Get memory for the handle, and the pattern. */
X    if ((dd = (DIR *)malloc(sizeof *dd)) == NULL) {
X	errno = ENOMEM;
X	return NULL;
X    }
X    dd->pattern = malloc((unsigned int)(strlen(name) + sizeof "*.*" + 1));
X    if (dd->pattern == NULL) {
X	free((char *)dd);
X	errno = ENOMEM;
X	return NULL;
X    }
X
X    /* Fill in the fields; mainly playing with the descriptor. */
X    (void)sprintf(dd->pattern, "%s*.*", name);
X    dd->context = 0;
X    dd->vms_wantversions = 0;
X    dd->pat.dsc$a_pointer = dd->pattern;
X    dd->pat.dsc$w_length = strlen(dd->pattern);
X    dd->pat.dsc$b_dtype = DSC$K_DTYPE_T;
X    dd->pat.dsc$b_class = DSC$K_CLASS_S;
X
X    return dd;
X}
X
X
X/*
X**  Set the flag to indicate we want versions or not.
X*/
Xvoid
Xvmsreaddirversions(dd, flag)
X    DIR		*dd;
X    int		flag;
X{
X    dd->vms_wantversions = flag;
X}
X
X
X/*
X**  Free up an opened directory.
X*/
Xvoid
Xclosedir(dd)
X    DIR		*dd;
X{
X    free(dd->pattern);
X    free((char *)dd);
X}
X
X
X/*
X**  Collect all the version numbers for the current file.
X*/
Xstatic void
Xcollectversions(dd)
X    DIR				*dd;
X{
X    struct dsc$descriptor_s	pat;
X    struct dsc$descriptor_s	res;
X    struct dirent		*e;
X    char			*p;
X    char			buff[sizeof dd->entry.d_name];
X    int				i;
X    char			*text;
X    long			context;
X
X    /* Convenient shorthand. */
X    e = &dd->entry;
X
X    /* Add the version wildcard, ignoring the "*.*" put on before */
X    i = strlen(dd->pattern);
X    text = malloc((unsigned int)(i + strlen(e->d_name)+ 2 + 1));
X    if (text == NULL)
X	return;
X    (void)strcpy(text, dd->pattern);
X    (void)sprintf(&text[i - 3], "%s;*", e->d_name);
X
X    /* Set up the pattern descriptor. */
X    pat.dsc$a_pointer = text;
X    pat.dsc$w_length = strlen(text);
X    pat.dsc$b_dtype = DSC$K_DTYPE_T;
X    pat.dsc$b_class = DSC$K_CLASS_S;
X
X    /* Set up result descriptor. */
X    res.dsc$a_pointer = buff;
X    res.dsc$w_length = sizeof buff - 2;
X    res.dsc$b_dtype = DSC$K_DTYPE_T;
X    res.dsc$b_class = DSC$K_CLASS_S;
X
X    /* Read files, collecting versions. */
X    for (context = 0; e->vms_verscount < VERSIZE(e); e->vms_verscount++) {
X	if (lib$find_file(&pat, &res, &context) == RMS$_NMF || context == 0)
X	    break;
X	buff[sizeof buff - 1] = '\0';
X	if (p = strchr(buff, ';'))
X	    e->vms_versions[e->vms_verscount] = atoi(p + 1);
X	else
X	    e->vms_versions[e->vms_verscount] = -1;
X    }
X
X    free(text);
X}
X
X
X/*
X**  Read the next entry from the directory.
X*/
Xstruct dirent *
Xreaddir(dd)
X    DIR				*dd;
X{
X    struct dsc$descriptor_s	res;
X    char			*p;
X    char			buff[sizeof dd->entry.d_name];
X    int				i;
X
X    /* Set up result descriptor, and get next file. */
X    res.dsc$a_pointer = buff;
X    res.dsc$w_length = sizeof buff - 2;
X    res.dsc$b_dtype = DSC$K_DTYPE_T;
X    res.dsc$b_class = DSC$K_CLASS_S;
X    if (lib$find_file(&dd->pat, &res, &dd->context) == RMS$_NMF
X     || dd->context == 0L)
X	/* None left... */
X	return NULL;
X
X    /* Force the buffer to end with a NUL. */
X    buff[sizeof buff - 1] = '\0';
X    for (p = buff; !isspace(*p); p++)
X	;
X    *p = '\0';
X
X    /* Skip any directory component and just copy the name. */
X    if (p = strchr(buff, ']'))
X	(void)strcpy(dd->entry.d_name, p + 1);
X    else
X	(void)strcpy(dd->entry.d_name, buff);
X
X    /* Clobber the version. */
X    if (p = strchr(dd->entry.d_name, ';'))
X	*p = '\0';
X
X    dd->entry.vms_verscount = 0;
X    if (dd->vms_wantversions)
X	collectversions(dd);
X    return &dd->entry;
X}
X
X
X/*
X**  Return something that can be used in a seekdir later.
X*/
Xlong
Xtelldir(dd)
X    DIR		*dd;
X{
X    return dd->context;
X}
X
X
X/*
X**  Return to a spot where we used to be.
X*/
Xvoid
Xseekdir(dd, pos)
X    DIR		*dd;
X    long	pos;
X{
X    dd->context = pos;
X}
X
X
X#ifdef	TEST
Xmain()
X{
X    char		buff[256];
X    DIR			*dd;
X    struct dirent	*dp;
X    int			i;
X    int			j;
X
X    for ( ; ; ) {
X	printf("\n\nEnter dir:  ");
X	(void)fflush(stdout);
X	(void)gets(buff);
X	if (buff[0] == '\0')
X	    break;
X	if ((dd = opendir(buff)) == NULL) {
X	    perror(buff);
X	    continue;
X	}
X
X	/* Print the directory contents twice, the second time print
X	 * the versions. */
X	for (i = 0; i < 2; i++) {
X	    while (dp = readdir(dd)) {
X		printf("%s%s", i ? "\t" : "    ", dp->d_name);
X		for (j = 0; j < dp->vms_verscount; j++)
X		    printf("  %d", dp->vms_versions[j]);
X		printf("\n");
X	    }
X	    rewinddir(dd);
X	    vmsreaddirversions(dd, 1);
X	}
X	closedir(dd);
X    }
X    exit(0);
X}
X#endif	/* TEST */
END_OF_FILE
  if test 5014 -ne `wc -c <'vmsreaddir.c'`; then
    echo shar: \"'vmsreaddir.c'\" unpacked with wrong size!
  fi
  # end of 'vmsreaddir.c'
fi
echo shar: End of archive.
exit 0
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.