The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
  Copyright (c) 1990-2002 Info-ZIP.  All rights reserved.

  See the accompanying file LICENSE, version 2000-Apr-09 or later
  (the contents of which are also included in zip.h) for terms of use.
  If, for some reason, all these files are missing, the Info-ZIP license
  also may be found at:  ftp://ftp.info-zip.org/pub/infozip/license.html
*/
/*
 * routines common to TANDEM (ZIP and UNZIP)
 */

#include "zip.h"   /* This sets up ZIP / UNZIP define */

#include <tal.h>
#include "$system.zsysdefs.zsysc" nolist
#include <cextdecs> nolist
#include "tannsk.h"

static time_t gmt_to_time_t (long long *);

int isatty (fnum)
int fnum;
{
  return 1;
}

/********************/
/* Function in2ex() */
/********************/

#ifdef UNZIP
char *in2ex(__G__ n)
  __GDEF
#else
char *in2ex(n)
#endif
  char *n;              /* internal file name */
/* Convert the zip file name to an external file name, returning the malloc'ed
   string or NULL if not enough memory. */
{
  char *x;              /* external file name buffer */
  char *y;              /* pointer to external buffer */
  char *max;            /* pointer to max end of next file part */
  char *t;              /* pointer to internal - start of substring */
  char *p;              /* pointer to internal - TANDEM delimiter */
  char *e;              /* pointer to internal - DOS extension delimiter */
  char *z;              /* pointer to internal - end of substring */
  int len;              /* length of substring to copy to external name */
  int allow_dollar;     /* $ symbol allowed as next character */

  if ((x = malloc(strlen(n) + 4)) == NULL)  /* + 4 for safety */
    return NULL;

  *x = '\0';

  /* Junk pathname as requested */
#ifdef UNZIP
  if (uO.jflag && (t = strrchr(n, INTERNAL_DELIMITER)) != NULL)
    ++t;
  else
    t = n;
#endif /* UNZIP */
#ifdef ZIP
  if (!pathput)
    t = last(n, INTERNAL_DELIMITER);
  else
    t = n;
#endif /* ZIP */

  allow_dollar = TRUE;

  while (*t != '\0') {  /* File part could be sys, vol, subvol or file */
    if (*t == INTERNAL_DELIMITER) {    /* System, Volume or Subvol Name */
      t++;
      if (*t == INTERNAL_DELIMITER) {  /* System */
        strcat(x, TANDEM_NODE_STR);
        t++;
      }
      else {
        strcat(x, TANDEM_DELIMITER_STR);
        allow_dollar = FALSE;
      }
    }
    /* Work out where end of current external string is */
    y = x + strlen(x);

    /* Work out substring to copy and externalise */
    p = strchr(t, INTERNAL_DELIMITER);
    e = strchr(t, DOS_EXTENSION);
    if (p != NULL) {
      if (e > p)
        e = NULL;
    }

    z = e;
    if (z == NULL)
      z = p;
    if (z == NULL)
      z = t + strlen(t);

    /* can't have Tandem name longer than 8 characters */
    max = y + MAXFILEPARTLEN;

    /* Allow $ symbol as first character in some cases */
    if (*t == '$') {
      if (allow_dollar)
        *y++ = *t++;
      else;
        *t++;
    }

    /* Make sure first real character is alpha */
    if (! isalpha(*t) )
      *y++ = 'A';

    /* Characters left to process */
    len = z - t;

    while ( len > 0 ) {
      if ( isalnum(*t) ) {
        *y++ = toupper(*t++);
        if (y >= max)
          break;
      }
      else
        t++;
      len--;
    }
    *y = '\0';
    t = p;

    if (p == NULL) {
      /* Last part of filename, store pseudo extension if available */
      if (e != NULL) {
        strcat(x, TANDEM_EXTENSION_STR);
        y = x + strlen(x);

        /* no restriction on extension length as its virtual */
        z = e + 1;
        while ( *z != '\0' ) {
          *y++ = toupper(*z++);
        }
        *y = '\0';
      }
      break;
    }
  }

  return x;
}

void zexit(status)
  int status;
{
  /* Exit(>0) creates saveabend files */
  terminate_program (0,0,(short)status,,,);
}

/************************/
/*  Function zputc()    */
/************************/

#ifdef putc
#  undef putc
#endif

int zputc(ch, fptr)
  int ch;
  FILE *fptr;
{
  int err;
  err = putc(ch,fptr);
  fflush(fptr);
  return err;
}
#define putc zputc

#ifdef LICENSED
_tal _priv short FILE_CHANGELABEL_ (
 short,          /* IN */
 short,          /* IN */
 const short _far *    /* IN */
 );

_c _callable int changelabel OF((short, const short *, const short *));

_c _callable int changelabel(fnum, modtime, actime)
  short fnum;
  const short *modtime;
  const short *actime;
{
  int err;

  err = FILE_CHANGELABEL_(fnum, 16, modtime);
  if (!err)
    err = FILE_CHANGELABEL_(fnum, 17, actime);
  return err;
}

int islicensed(void)
{
  #define plist_items 1
  #define plist_size 10

  short myphandle[ZSYS_VAL_PHANDLE_WLEN];
  short licensetag[plist_items] = {37};
  short licensed[plist_size];
  short maxlen = plist_size;
  short items = plist_items;
  short resultlen[1], err;

  err = PROCESSHANDLE_NULLIT_(myphandle);

  if (!err)
    err = PROCESS_GETINFO_(myphandle);

  if (!err)
    err = PROCESS_GETINFOLIST_(/*cpu*/,
                               /*pin*/,
                               /*nodename*/,
                               /*nodenamelen*/,
                               myphandle,
                               licensetag,
                               items,
                               licensed,
                               maxlen,
                               resultlen
                              );

  if (err != 0)
    return 0;
  else
    return licensed[0];
}
#endif /* LICENSED */

int utime(file, time)
  const char *file;
  const ztimbuf *time;
{
#ifdef LICENSED
  int result, err;
  union timestamp_ov {
    long long fulltime;
    short wordtime[4];
  };
  union timestamp_ov lasttime, opentime;
  struct tm *modt, *opent;
  short datetime[8], errormask[1];
  short len, fnum, access, exclus, options;
  char fname[FILENAME_MAX + 1];
  short extension;
  char ext[EXTENSION_MAX + 1];

  if (islicensed() ) {
    /* Attempt to update file label */
    modt = gmtime( &time->modtime );

    datetime[0] = modt->tm_year + 1900;
    datetime[1] = modt->tm_mon + 1;
    datetime[2] = modt->tm_mday;
    datetime[3] = modt->tm_hour;
    datetime[4] = modt->tm_min;
    datetime[5] = modt->tm_sec;
    datetime[6] = datetime[7] = 0;
    errormask[0] = 0;
    lasttime.fulltime = COMPUTETIMESTAMP (datetime, errormask);

    opent = gmtime( &time->actime );

    datetime[0] = opent->tm_year + 1900;
    datetime[1] = opent->tm_mon + 1;
    datetime[2] = opent->tm_mday;
    datetime[3] = opent->tm_hour;
    datetime[4] = opent->tm_min;
    datetime[5] = opent->tm_sec;
    datetime[6] = datetime[7] = 0;
    errormask[0] = 0;
    opentime.fulltime = COMPUTETIMESTAMP (datetime, errormask);

    /* Remove any (pseudo) file extension */
    extension = parsename (file,fname,ext);
    len = strlen(fname);

    access = NSK_WRONLY;
    exclus = NSK_SHARED;
    options = NSK_NOUPDATEOPENTIME;

    extension = parsename (file,fname,ext);
    len = strlen(fname);

    err = FILE_OPEN_((char *)fname, len, &fnum, access, exclus,,,options,,,);
    result = changelabel(fnum,lasttime.wordtime,opentime.wordtime);
    err = FILE_CLOSE_(fnum);
    return result;
  }
  return -1;
#else  /* !LICENSED */
  return 0;             /* "no error", to suppress annoying failure messages */
#endif  /* ?LICENSED */
}

/* TANDEM version of chmod() function */

int chmod(file, unix_sec)
  const char *file;
  mode_t unix_sec;
{
  FILE *stream;
  struct nsk_sec_type {
    unsigned progid : 1;
    unsigned clear  : 1;
    unsigned null   : 2;
    unsigned read   : 3;
    unsigned write  : 3;
    unsigned execute: 3;
    unsigned purge  : 3;
  };
  union nsk_sec_ov {
    struct nsk_sec_type bit_ov;
    short int_ov;
  };
  union nsk_sec_ov nsk_sec;
  short fnum, err, nsk_sec_int;
  short len, access, exclus, extension, options;
  char fname[FILENAME_MAX + 1];
  char ext[EXTENSION_MAX + 1];

  nsk_sec.bit_ov.progid = 0;
  nsk_sec.bit_ov.clear  = 0;
  nsk_sec.bit_ov.null   = 0;

  /*  4="N", 5="C", 6="U", 7="-"   */

  if (unix_sec & S_IROTH) nsk_sec.bit_ov.read = 4;
  else if (unix_sec & S_IRGRP) nsk_sec.bit_ov.read = 5;
  else if (unix_sec & S_IRUSR) nsk_sec.bit_ov.read = 6;
  else nsk_sec.bit_ov.read = 7;

  if (unix_sec & S_IWOTH) nsk_sec.bit_ov.write = 4;
  else if (unix_sec & S_IWGRP) nsk_sec.bit_ov.write = 5;
  else if (unix_sec & S_IWUSR) nsk_sec.bit_ov.write = 6;
  else nsk_sec.bit_ov.write = 7;

  if (unix_sec & S_IXOTH) nsk_sec.bit_ov.execute = 4;
  else if (unix_sec & S_IXGRP) nsk_sec.bit_ov.execute = 5;
  else if (unix_sec & S_IXUSR) nsk_sec.bit_ov.execute = 6;
  else nsk_sec.bit_ov.execute = 7;

  nsk_sec.bit_ov.purge = nsk_sec.bit_ov.write;

  nsk_sec_int = nsk_sec.int_ov;

  access = NSK_RDONLY;
  exclus = NSK_SHARED;
  options = NSK_NOUPDATEOPENTIME;

  extension = parsename (file,fname,ext);
  len = strlen(fname);

  err = FILE_OPEN_((char *)fname, len, &fnum, access, exclus,,,options,,,);
  err = (SETMODE(fnum, SET_FILE_SECURITY, nsk_sec_int) != CCE);
  err = FILE_CLOSE_(fnum);

  return (err != 0 ? -1 : 0);
}

/* TANDEM version of chown() function */

int chown(file, uid, gid)
  const char *file;
  uid_t uid;
  gid_t gid;
{
  FILE *stream;
  struct nsk_own_type {
    unsigned group  : 8;
    unsigned user   : 8;
  };
  union nsk_own_ov {
    struct nsk_own_type bit_ov;
    short int_ov;
  };
  union nsk_own_ov nsk_own;
  short fnum, err, nsk_own_int;
  short len, access, exclus, extension, options;
  char fname[FILENAME_MAX + 1];
  char ext[EXTENSION_MAX + 1];

  nsk_own.bit_ov.group = gid;
  nsk_own.bit_ov.user  = uid;

  nsk_own_int = nsk_own.int_ov;

  access = NSK_RDONLY;
  exclus = NSK_SHARED;
  options = NSK_NOUPDATEOPENTIME;

  extension = parsename (file,fname,ext);
  len = strlen(fname);

  err = FILE_OPEN_((char *)fname, len, &fnum, access, exclus,,,options,,,);
  err = (SETMODE(fnum, SET_FILE_OWNER, nsk_own_int) != CCE);
  err = FILE_CLOSE_(fnum);
  return (err != 0 ? -1 : 0);
}

/* TANDEM version of getch() - non-echo character reading */
int zgetch(void)
{
  char ch;
  short f, err, count, fnum, rlen;

  rlen = 1;
  f = (short)fileno(stdin);
  fnum = fdtogfn (f);
  #define ECHO_MODE 20
  err = (SETMODE(fnum, ECHO_MODE, 0) != CCE);
  err = (READX(fnum, &ch, rlen, (short *) &count) != CCE);
  err = (SETMODE(fnum, ECHO_MODE, 1) != CCE);

  if (err)
    if (err != 1)
      return EOF;
    else
      ch = 'q';
  else
    if (count == 0)
      ch = '\r';

  return (int)ch;
}

short parsename(srce, fname, ext)
  const char *srce;
  char *fname;
  char *ext;
{
  /* As a way of supporting DOS extensions from Tandem we look for a space
     separated extension string after the Guardian filename
     e.g. ZIP ZIPFILE "$DATA4.TESTING.INVOICE TXT"
  */

  char *fstart;
  char *fptr;
  short extension = 0;

  *fname = *ext = '\0';  /* set to null string */

  fstart = (char *) srce;

  if ((fptr = strrchr(fstart, TANDEM_EXTENSION)) != NULL) {
    extension = 1;

    fptr++;
    strncat(ext, fptr, _min(EXTENSION_MAX, strlen(fptr)));

    fptr = strchr(fstart, TANDEM_EXTENSION);  /* End of filename */
    strncat(fname, fstart, _min(FILENAME_MAX, (fptr - fstart)));
  }
  else {
    /* just copy string */
    strncat(fname, srce, _min(FILENAME_MAX, strlen(srce)));
  }

  return extension;
}

static time_t gmt_to_time_t (gmt)
  long long *gmt;
{
  #define GMT_TO_LCT 0
  #define GMT_TO_LST 1

  struct tm temp_tm;
  short  date_time[8];
  long   julian_dayno;
  long long lct, lst, itime;
  short  err[1], type;

  type = GMT_TO_LCT;
  lct = CONVERTTIMESTAMP(*gmt, type,, err);

  if (!err[0]) {
    type = GMT_TO_LST;
    lst = CONVERTTIMESTAMP(*gmt, type,, err);
  }

  itime = (err[0] ? *gmt : lct);
  /* If we have no DST in force then make sure we give it a value,
     else mktime screws up if we set the isdst flag to -1 */
  temp_tm.tm_isdst = (err[0] ? 0 : ((lct == lst) ? 0 : 1));

  julian_dayno = INTERPRETTIMESTAMP(itime, date_time);

  temp_tm.tm_sec   = date_time[5];
  temp_tm.tm_min   = date_time[4];
  temp_tm.tm_hour  = date_time[3];
  temp_tm.tm_mday  = date_time[2];
  temp_tm.tm_mon   = date_time[1] - 1;     /* C's so sad */
  temp_tm.tm_year  = date_time[0] - 1900;  /* it's almost funny */

  return (mktime(&temp_tm));
}

/* TANDEM version of stat() function */
int stat(n, s)
  const char *n;
  struct stat *s;
{
  #define ilist_items 26
  #define klist_items 4
  #define slist_items 3
  #define ulist_items 1
  #define flist_size 100

  short err, i, extension;
  char fname[FILENAME_MAX + 1];
  short fnamelen;
  char ext[EXTENSION_MAX + 1];

                         /* #0  #1  #2  #3  #4  #5  #6  #7  #8  #9 */
  short ilist[ilist_items]={56,144, 54,142, 58, 62, 60, 41, 42, 44,
                            50, 51, 52, 61, 63, 66, 67, 70, 72, 73,
                            74, 75, 76, 77, 78, 79                 };
  short ilen[ilist_items] ={ 4,  4,  4,  2,  1,  2,  1,  1,  1,  1,
                             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
                             1,  1,  1,  1,  1,  1                 };
  short ioff[ilist_items];

                         /* #0  #1  #2  #3  #4  #5  #6  #7  #8  #9 */
  short klist[klist_items]={45, 46, 68, 69                         };
  short klen[klist_items] ={ 1,  1,  1,  1                         };
  short koff[klist_items];

                         /* #0  #1  #2  #3  #4  #5  #6  #7  #8  #9 */
  short slist[slist_items]={43, 80, 90                             };
  short slen[slist_items] ={ 1,  1,  1                             };
  short soff[slist_items];

                         /* #0  #1  #2  #3  #4  #5  #6  #7  #8  #9 */
  short ulist[ulist_items]={65                                     };
  short ulen[ulist_items] ={ 1                                     };
  short uoff[ulist_items];

  short flist[flist_size];
  short extra[2];
  short *rlen=&extra[0];
  short *err_item=&extra[1];
  unsigned short *fowner;
  unsigned short *fprogid;
  char *fsec;

  nsk_stat_ov *nsk_ov;
  nsk_file_attrs *nsk_attr;

  short end, count, kind, level, options, searchid;
  short info[5];

  /* Initialise stat structure */
  s->st_dev = _S_GUARDIANOBJECT;
  s->st_ino = 0;
  s->st_nlink = 0;
  s->st_rdev = 0;
  s->st_uid = s->st_gid = 0;
  s->st_size = 0;
  s->st_atime = s->st_ctime = s->st_mtime = 0;
  s->st_reserved[0] = 0;
  s->st_reserved[1] = 0;
  s->st_reserved[2] = 0;
  nsk_ov = (nsk_stat_ov *)&s->st_reserved[0];
  nsk_attr = (nsk_file_attrs *)&nsk_ov->ov.nsk_ef_region;

  /* Check to see if name contains a (pseudo) file extension */
  extension = parsename (n,fname,ext);

  fnamelen = strlen(fname);

  options = 3; /* Allow Subvols and Templates */
  err = FILENAME_SCAN_( fname,
                        fnamelen,
                        &count,
                        &kind,
                        &level,
                        options
                      );

  /* allow kind == 2 (DEFINE names) */
  if (err != 0) return -1;

  if (kind == 1 || (kind == 0 && level < 2)) {
    /* Pattern, Subvol Name or One part Filename - lets see if it exists */
    err = FILENAME_FINDSTART_ ( &searchid,
                                fname,
                                fnamelen,
                                ,
                                DISK_DEVICE
                              );

    if (err != 0) {
      end = FILENAME_FINDFINISH_ ( searchid );
      return -1;
    }

    err = FILENAME_FINDNEXT_ ( searchid,
                               fname,
                               FILENAME_MAX,
                               &fnamelen,
                               info
                              );
    end = FILENAME_FINDFINISH_ ( searchid );

    if (err != 0)
      return -1;  /* Non existing template, subvol or file */

    if (kind == 1 || info[2] == -1) {
      s->st_mode = S_IFDIR;    /* Its an existing template or directory */
      return 0;
    }

    /* Must be a real file so drop to code below to get info on it */
  }

  err = FILE_GETINFOLISTBYNAME_( fname,
                                 fnamelen,
                                 ilist,
                                 ilist_items,
                                 flist,
                                 flist_size,
                                 rlen,
                                 err_item
                               );
  if (err != 0) return -1;

  ioff[0] = 0;

  /*  Build up table of offets into result list */
  for (i=1; i < ilist_items; i++)
    ioff[i] = ioff[i-1] + ilen[i-1];

  /* Set up main stat fields */

  /* Setup timestamps */
  s->st_atime = gmt_to_time_t ((long long *)&flist[ioff[0]]);
  s->st_mtime = s->st_ctime = gmt_to_time_t ((long long *)&flist[ioff[1]]);
  nsk_ov->ov.creation_time = gmt_to_time_t ((long long *)&flist[ioff[2]]);

  s->st_size = *(off_t *)&flist[ioff[3]];

  fowner = (unsigned short *)&flist[ioff[4]];
  s->st_uid = *fowner & 0x00ff;
  s->st_gid = *fowner >> 8;

  /* Note that Purge security (fsec[3]) in NSK has no relevance to stat() */
  fsec = (char *)&flist[ioff[5]];
  fprogid = (unsigned short *)&flist[ioff[6]];

  s->st_mode = S_IFREG |  /* Regular File */
  /*  Parse Read Flag */
               ((fsec[0] & 0x03) == 0x00 ? S_IROTH : 0) |
               ((fsec[0] & 0x02) == 0x00 ? S_IRGRP : 0) |
               ((fsec[0] & 0x03) != 0x03 ? S_IRUSR : 0) |
  /*  Parse Write Flag */
               ((fsec[1] & 0x03) == 0x00 ? S_IWOTH : 0) |
               ((fsec[1] & 0x02) == 0x00 ? S_IWGRP : 0) |
               ((fsec[1] & 0x03) != 0x03 ? S_IWUSR : 0) |
  /*  Parse Execute Flag */
               ((fsec[2] & 0x03) == 0x00 ? S_IXOTH : 0) |
               ((fsec[2] & 0x02) == 0x00 ? S_IXGRP : 0) |
               ((fsec[2] & 0x03) != 0x03 ? S_IXUSR : 0) |
  /*  Parse Progid */
               (*fprogid == 1 ? (S_ISUID | S_ISGID) : 0) ;

  /* Set up NSK additional stat fields */
  nsk_attr->progid   = (unsigned) flist[ioff[6]];
  nsk_attr->filetype = (unsigned) flist[ioff[7]];
  nsk_attr->filecode = (unsigned) flist[ioff[8]];
  nsk_attr->block    = (unsigned short) flist[ioff[9]];
  nsk_attr->priext   = (unsigned short) flist[ioff[10]];
  nsk_attr->secext   = (unsigned short) flist[ioff[11]];
  nsk_attr->maxext   = (unsigned short) flist[ioff[12]];
  nsk_attr->flags.clearonpurge = (unsigned) flist[ioff[13]];
  nsk_attr->licensed     = (unsigned) flist[ioff[14]];
  nsk_attr->flags.audited      = (unsigned) flist[ioff[15]];
  nsk_attr->flags.acompress    = (unsigned) flist[ioff[16]];
  nsk_attr->flags.refresheof   = (unsigned) flist[ioff[17]];
  nsk_attr->flags.buffered     = (unsigned) (flist[ioff[18]] == 0 ? 1 : 0);
  nsk_attr->flags.verified     = (unsigned) flist[ioff[19]];
  nsk_attr->flags.serial       = (unsigned) flist[ioff[20]];
  nsk_attr->flags.crashopen    = (unsigned) flist[ioff[22]];
  nsk_attr->flags.rollforward  = (unsigned) flist[ioff[23]];
  nsk_attr->flags.broken       = (unsigned) flist[ioff[24]];
  nsk_attr->flags.corrupt      = (unsigned) flist[ioff[25]];
  nsk_attr->fileopen     = (unsigned) flist[ioff[21]];


  if (nsk_attr->filetype == NSK_UNSTRUCTURED) {
    /* extra info for Unstructured files */
    err = FILE_GETINFOLISTBYNAME_( fname,
                                   fnamelen,
                                   ulist,
                                   ulist_items,
                                   flist,
                                   flist_size,
                                   rlen,
                                   err_item
                                 );
    if (err != 0) return -1;

    uoff[0] = 0;

    /*  Build up table of offets into result list */
    for (i=1; i < ulist_items; i++)
      uoff[i] = uoff[i-1] + ulen[i-1];
  }
  else {
    /* extra info for Structured files */
    err = FILE_GETINFOLISTBYNAME_( fname,
                                   fnamelen,
                                   slist,
                                   slist_items,
                                   flist,
                                   flist_size,
                                   rlen,
                                   err_item
                                 );
    if (err != 0) return -1;

    soff[0] = 0;

    /*  Build up table of offets into result list */
    for (i=1; i < slist_items; i++)
      soff[i] = soff[i-1] + slen[i-1];

    nsk_attr->reclen   = (unsigned) flist[soff[0]];
    nsk_attr->flags.secpart   = (unsigned) flist[soff[1]];
    nsk_attr->flags.primpart  = (unsigned)
     ( (flist[soff[2]] > 0 && nsk_attr->flags.secpart == 0) ? 1 : 0 );

    if (nsk_attr->filetype == NSK_KEYSEQUENCED) {
      /* extra info for Key Sequenced files */
      err = FILE_GETINFOLISTBYNAME_( fname,
                                     fnamelen,
                                     klist,
                                     klist_items,
                                     flist,
                                     flist_size,
                                     rlen,
                                     err_item
                                   );
      if (err != 0) return -1;

      koff[0] = 0;

      /*  Build up table of offets into result list */
      for (i=1; i < klist_items; i++)
        koff[i] = koff[i-1] + klen[i-1];

      nsk_attr->keyoff   = (unsigned) flist[koff[0]];
      nsk_attr->keylen   = (unsigned) flist[koff[1]];
      nsk_attr->flags.dcompress = (unsigned) flist[koff[2]];
      nsk_attr->flags.icompress = (unsigned) flist[koff[3]];
    }
  }

  return 0;
}

#ifndef SFX
/* TANDEM Directory processing */

DIR *opendir(const char *dirname)
{
   short i, resolve;
   char sname[FILENAME_MAX + 1];
   short snamelen;
   char fname[FILENAME_MAX + 1];
   short fnamelen;
   char *p;
   short searchid, err, end;
   struct dirent *entry;
   DIR *dirp;
   char ext[EXTENSION_MAX + 1];
   short extension;

   extension = parsename(dirname, sname, ext);
   snamelen = strlen(sname);

   /*  First we work out how detailed the template is...
    *  e.g. If the template is DAVES*.* we want the search result
    *       in the same format
    */

   p = sname;
   i = 0;
   while ((p = strchr(p, TANDEM_DELIMITER)) != NULL){
     i++;
     p++;
   };
   resolve = 2 - i;

   /*  Attempt to start a filename template */
   err = FILENAME_FINDSTART_ ( &searchid,
                               sname,
                               snamelen,
                               resolve,
                               DISK_DEVICE
                             );
   if (err != 0) {
     end = FILENAME_FINDFINISH_(searchid);
     return NULL;
   }

   /* Create DIR structure */
   if ((dirp = malloc(sizeof(DIR))) == NULL ) {
     end = FILENAME_FINDFINISH_(searchid);
     return NULL;
   }
   dirp->D_list = dirp->D_curpos = NULL;
   strcpy(dirp->D_path, dirname);

   while ((err = FILENAME_FINDNEXT_(searchid,
                                    fname,
                                    FILENAME_MAX,
                                    &fnamelen
                                   )
           ) == 0 ){
     /*  Create space for entry */
     if ((entry = malloc (sizeof(struct dirent))) == NULL) {
       end = FILENAME_FINDFINISH_(searchid);
       return NULL;
     }

     /*  Link to last entry */
     if (dirp->D_curpos == NULL)
       dirp->D_list = dirp->D_curpos = entry;  /* First name */
     else {
       dirp->D_curpos->d_next = entry;         /* Link */
       dirp->D_curpos = entry;
     };
     /* Add directory entry */
     *dirp->D_curpos->d_name = '\0';
     strncat(dirp->D_curpos->d_name,fname,fnamelen);
     if (extension) {
       strcat(dirp->D_curpos->d_name,TANDEM_EXTENSION_STR);
       strcat(dirp->D_curpos->d_name,ext);
     };
     dirp->D_curpos->d_next = NULL;
   };

   end = FILENAME_FINDFINISH_(searchid);

   if (err == 1) {  /*  Should return EOF at end of search */
     dirp->D_curpos = dirp->D_list;        /* Set current pos to start */
     return dirp;
   }
   else
     return NULL;
}

struct dirent *readdir(DIR *dirp)
{
   struct dirent *cur;

   cur = dirp->D_curpos;
   dirp->D_curpos = dirp->D_curpos->d_next;
   return cur;
}

void rewinddir(DIR *dirp)
{
   dirp->D_curpos = dirp->D_list;
}

int closedir(DIR *dirp)
{
   struct dirent *node;

   while (dirp->D_list != NULL) {
      node = dirp->D_list;
      dirp->D_list = dirp->D_list->d_next;
      free( node );
   }
   free( dirp );
   return 0;
}

#endif /* !SFX */