/*
**  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 */
