static char rcsid[] = "$Id: fxpand.c,v 1.1 1992/09/06 19:31:32 mike Exp $";

/* $Log: fxpand.c,v $
 * Revision 1.1  1992/09/06  19:31:32  mike
 * Initial revision
 *
 */

#if 0		/* there are star-slashes in the comment below */
/* fxpand.c : fxpand(): expand file names
 * Input:
 *   name:  A file name which can contain: ?, \, [], [^] and *
 *     If name ends with "." (eg *.) then only names with no extensions will
 *       be matched.
 *     If name beings with ~ then:  "~/" expands to "$(HOME)/" and "~name"
 *	 expands to "<home directory of name>".
 *     MS-DOS:  \ is same as /.  Does not quote.
 *     UNIX: \ quotes the next character.
 *     ATARI (from jwahar r.  bammi (bammi@cadence.com)):
 *       Like MS-DOS, \ == /, except that we have the POSIX opendir() etc.
 *   onlyone: If TRUE return the first match otherwise all matches are
 *     returned.  For example, this controls how "*" is expanded.
 *   nopath:  If TRUE, only return the matching file names.  Otherwise
 *     return the path and name.  For example, "/foo/bar" => "bar".
 *   slash_dir:  If TRUE, append a slash to the end of a file name if the
 *     file is a directory.  For example, input of "dir" would generate
 *     "dir/".
 *   name_heap:
 *     NULL: Call process_fname.
 *     Pointer to char[]:  Stuff the file names in here.  The names are
 *	separated by a blank and there are no trailing blanks.
 *   process_fname:  A function which is passed a char *.  The string
 *       contains the expanded file name.
 *     Returns:  0 if all OK, >1 an error to be returned by fxpand().
 * Output:
 *   0:  All OK
 *   1:  Something screwed up.  Most likely the name is bad.
 *   n:  An error code returned by process_fname().
 * 
 * Notes:
 *   When compiled on Apollo, this routine also works with the Domain/OS
 *     "//" notation.  This is mostly luck - I don't collapse "/"s and a
 *     relaxed check lets this work.
 *   Input error checking is pretty grim.
 * Unix Notes:
 *   When wildcard matching, hidden files (those that start with ".")  are
 *     skipped unless you ask to see them.  To match ".fred", you could use
 *     ".f*".  To match "fred/.sam/geo", you would need something like
 *     "fred/.s*/g*".
 *   When appending slashes (slash_dir), expanding something like "*" can be
 *     very slow.  This is because I have to stat() to find out if the file
 *     is a directory and stat() can take a long time to stat files over nfs
 *     mounts, follow links, etc.
 * 
 * Craig Durland
 *
 * Do not delete this double quote! " (You are not expected to understand this)
 */
#endif

/* Copyright 1989, 1990, 1991 Craig Durland
 *   Distributed under the terms of the GNU General Public License.
 *   Distributed "as is", without warranties of any kind, but comments,
 *     suggestions and bug reports are welcome.
 */

#define _HPUX_SOURCE		/* for ANSI C on HP-UX */
#define _BSD_SOURCE		/* for ANSI C on Apollo BSD */
#define _POSIX_SOURCE		/* for ANSI C on IBM AIX */

#include <stdio.h>
#include "os.h"
#include "const.h"

extern char *strchr(), *getenv(), *strcpy(), *strcat();

static char *name_list;
static int prepend_blank;
static int stuff_name(name) char *name;
{
  if (prepend_blank) strcat(name_list," ");
  else prepend_blank = TRUE;

  strcat(name_list,name);
  return 0;
}

#if MSDOZ

#define SLASH	"/"		/* "/" or "\\", your preference */

    /* A note about MS-DOS:  Its file find routines are brain dead:  If you
     *   ask for a directory, you will get all file types.  Also, since
     *   there is a file type==0, you can't just use AND to filter out
     *   unwanted types.  What a pain in the butt!
     */

#include <dos.h>
#ifdef __TSC__ 	    /* ibmdir.h has definitions for Lattice  C structures */
#include "ibmdir.h"
#include "char.h"
#endif	/* __TSC__ */

typedef struct FILEINFO FI;	/* in dos.h */

#define FATTR (FA_ARCHIVE | FA_RD_ONLY | FA_DIRECTORY)	/* file attributes */


fxpand(name, onlyone,nopath,slash_dir, name_heap,process_fname)
  char *name,*name_heap; pfi process_fname;
{
  char path[128], word[100], *ptr, *qtr, tbuf[128];
  FI de;
  int atend, eval, found, needadot, path_len, s;

  if (name_heap)	/* store the found file names in name_heap */
  {
    process_fname = stuff_name;
    *(name_list = name_heap) = '\0';
    prepend_blank = FALSE;
  }

  *path = '\0';
  if (*name == '~')
  {
    name++;
    if (ptr = getenv("HOME")) strcpy(path,ptr);
  }
  else
    if (name[1] == ':')
	{ strncpy(path,name,2); path[2] = '\0'; name += 2; }


  atend = FALSE; needadot = FALSE;
  while (!atend)
  {
    atend = get_dpart(&name,word,&eval);
    path_len = strlen(path);
uppercase(word);	/* Since the directory entries are uppercase */
    if (eval)			/* wildcards in need of expansion */
    {
if (word[strlen(word)-1] == '.') needadot = TRUE;
      found = FALSE;
      ptr = path +path_len; strcpy(ptr,"*.*");

      if (dfind(&de,path,FATTR)) return 1;	/* No files found */
      *ptr = '\0';		/* get rid of "*.*" */
      do		/* look at all entries in this directory */
      {
	if (!atend)			/* only care about directories */
	  { if (de.attr != FA_DIRECTORY) continue; }
        else
	  if (de.attr &&	/* if de.attr==0, its a regular old file */
	      (de.attr & FATTR) == 0) continue;

	ptr = qtr = de.name;
if (needadot && !strchr(ptr,'.')) ptr = strcat(strcpy(tbuf,ptr),".");
	if (*qtr != '.' && wildmat(ptr,word))	/* ignore . & .. */
	{
	  found = TRUE;
	  if (!atend)		/* something like foo/<you are here>/bar */
	  {
	    strcat(strcat(path,qtr), SLASH);
	    break;
	  }
	  else			/* something like foo/<you are here> */
	  {
	    strcpy(path +path_len, qtr);
	    s = procz(process_fname, slash_dir && (de.attr == FA_DIRECTORY),
			path, nopath ? path_len : 0);
	    path[path_len] = '\0';
	    if (s) return s;
	    if (onlyone) break;
	  }
	}
      } while (!dnext(&de));
      if (!found) return 1;
    }
    else
    {
      strcpy(path + path_len, word);
      if (atend)	/* all done */
	return procz(process_fname, slash_dir && is_dir(path),
			path, nopath ? path_len : 0);
/* ??? if (!is_dir(path)) return 1; /* Make sure path is real */
    } 
  }	/* end while */
  return 0;
}

static int procz(process_fname, slash_it, path, path_len)
  pfi process_fname; char *path;
{
  if (slash_it && (path[path_len] != '\0')) strcat(path, "/");
  return (*process_fname)(path + path_len);
}

    /* 
     * Notes:
     *   For some (unknown to me) reason, FA_DIRECTORY matches everything,
     *     not just directories.
     * WARNING:
     *   This routine changes the DTA.  If you are in the middle of a
     *     dfind/dnext loop, this will mess that up.
     */
static int is_dir(path) char *path;
{
  FI de;

  return (!dfind(&de,path,FA_DIRECTORY) && (de.attr == FA_DIRECTORY));
}



	/* MS-DOS stuff for get_dpart() */
#define ASLASH		case '/': case '\\'
#define GOTTA_EVAL	case '?': case '[': case '*'

#endif	/* MSDOZ */


#if ATARI    /* Atari has Posix opendir(), etc */
#undef  POSIX_OS
#define POSIX_OS 1	/* turn on POSIX and UX_OS */
#endif	/* ATARI */





#if UX_OS	/* and Atari */

#define BADGETPW 1	/* 1 if system getpw... routines are screwed up */

#include <sys/types.h>
#include <pwd.h>
#include <sys/stat.h>

	/* Posix, SysV: HP-UX, Apollo SysV, DEC, Atari  */
	/* defined(POSIX) is a DECism */
#if POSIX_OS || SYSV_OS || defined(POSIX)
#include <dirent.h>
#else		/* Pure BSD: Apollo bsd4.3 */
#include <sys/dir.h>
#endif


static int get_dpart(), getpwhome(), procz(), is_dir();


    /* cases to check:
     *   "~", "~fred", "/", "~/", ""
     */
fxpand(name, onlyone,nopath,slash_dir, name_heap,process_fname)
  char *name,*name_heap; pfi process_fname;
{
  char path[512], word[256], *ptr, *qtr, tbuf[256];
  DIR *dir;
#if POSIX_OS || SYSV_OS || defined(POSIX)
  struct dirent *dtr;
#else		/* Apollo bsd4.3, (some)DEC */
  struct direct *dtr;
#endif
  int
    atend, needadot,
    eval, found, path_len, s,
    skip_dot_files;		/* ignore names starting with "." */
  struct passwd *pd;

  if (name_heap)	/* store the found file names in name_heap */
  {
    process_fname = stuff_name;
    *(name_list = name_heap) = '\0';
    prepend_blank = FALSE;
  }

  *path = '\0';
  if (*name == '~')		/* csh/ksh home directory expansion */
  {
    name++;
    if (ISSLASH(*name) || *name == '\0')	/* ~/foo/bar or ~ */
    {
      if (ptr = getenv("HOME")) strcpy(path,ptr);
      else		/* no $HOME, see if the OS knows */
      {
#if BADGETPW
	return 1;	/* !!! a sleeze */
#else
	if ((pd = getpwuid(getuid())) == NULL) return 1;
	strcpy(path,pd->pw_dir);
#endif	/* BADGETPW */
      }
    }
    else			/* ~fred => user freds' home directory */
    {
      atend = get_dpart(&name,word,&eval);
      if (eval) return 1;	    /* no wildcards allowed in user name */
      name--;
      if (!atend) word[strlen(word)-1] = '\0';	/* remove "/" from  "~fred/" */
#if BADGETPW
      if (!getpwhome(word)) return 1;
      strcpy(path,word);
#else
      if ((pd = getpwnam(word)) == NULL) return 1;
      strcpy(path,pd->pw_dir);
#endif	/* BADGETPW */
    }
  }

	/* at this point, maybe: strlen(path)!=0 && strlen(name)==0 */

  atend = FALSE; needadot = FALSE;
  while (!atend)
  {
    atend = get_dpart(&name,word,&eval);
    skip_dot_files = (*word != '.');	/* ".fred" means look at dot files */
    path_len = strlen(path);
    if (eval)			/* wildcards in need of expansion */
    {
if (word[strlen(word)-1] == '.') needadot = TRUE;
      found = FALSE;
      if ((dir = opendir(path_len == 0 ? "." : path)) == NULL) return 1;
      while (TRUE)		/* look at all entries in this directory */
      {
	if ((dtr = readdir(dir)) == NULL) break;
	ptr = qtr = dtr->d_name;
	if (skip_dot_files && *ptr == '.') continue;
if (needadot && !strchr(ptr,'.')) ptr = strcat(strcpy(tbuf,ptr),".");
	if (wildmat(ptr,word))
	{
	  if (!atend)		/* something like foo/<you are here>/bar */
	  {
	    strcpy(path + path_len, qtr);
			/* make sure its a real directory */
	    if (is_dir(path)) { strcat(path,"/"); found = TRUE; break; }
	  }
	  else			/* something like foo/<you are here> */
	  {
	    found = TRUE;
	    strcpy(path +path_len, qtr);
	    s = procz(process_fname, slash_dir, path, nopath ? path_len : 0);
	    path[path_len] = '\0';
	    if (s) { closedir(dir); return s; }
	    if (onlyone) break;
	  }
	}
      }		/* end while (TRUE) */
      closedir(dir);
      if (!found) return 1;
    }
    else	/* No wildcards: something like: .../bar/... or .../bar */
    {		/* word may == "" (for input like "~fred") */
      strcpy(path + path_len, word);
      if (atend)	/* all done */
	return procz(process_fname, slash_dir, path, nopath ? path_len : 0);

/* ??? if (!is_dir(path)) return 1; /* Make sure path is real */
    }
  }		/* end while */
  return 0;
}


    /* Input:
     *   process_fname:  pointer to function to call
     *   slash_dir: TRUE:  if path is a directory, append a slash
     *   path:  Full path name
     *   path_len:  offset in path.  Used if what to pass just part of path
     *     to process_fname.
     * Returns:
     *   What ever process_fname returns.
     * Notes:
     *   Need to check path[path_len] before appending a slash because:  if
     *     don't want a path (path_len != 0) and no name (eg expanding "~"),
     *     would process_fname("/") which is not what is expected.
     */
static int procz(process_fname, slash_dir, path, path_len)
  pfi process_fname; char *path;
{
  if (slash_dir && (path[path_len] != '\0') && is_dir(path))
	strcat(path, "/");
  return (*process_fname)(path + path_len);
}


static int is_dir(path) char *path;
{
  struct stat sbuf;

  if (stat(path,&sbuf)) return FALSE;
	/* make sure its a directory */
#ifdef S_ISDIR
  return S_ISDIR(sbuf.st_mode);
#else
  return ((sbuf.st_mode & 0170000) == 040000);
#endif	/* S_ISDIR */
}


#if BADGETPW
	/* Get the home directory out of the password file.
	 * Only use this if the system getpw... routines are
	 *   screwed up.
	 */
static int getpwhome(name) char *name;
{
  char buf[256], *ptr;
  FILE *fptr;
  int n, s = FALSE;

  if ((fptr = fopen("/etc/passwd","r")) == NULL) return FALSE;
  while (fgets(buf,255,fptr))
  {
    for (ptr = buf; *ptr != ':'; ptr++) ; *ptr = '\0';
    if (strcmp(name,buf)) continue;
    for (n = 0; n < 4; ptr++) if (*ptr == ':') n++;
    while (*ptr != ':') *name++ = *ptr++;   *name = '\0';
    s = TRUE;
    break;
  }
  fclose(fptr);
  return s;
}
#endif	/* BADGETPW */

#if ATARI	/* Atari stuff for get_dpart(), same as MS-DOS */
#define ASLASH		case '/': case '\\'
#define GOTTA_EVAL	case '?': case '[': case '*'
#else
	/* UNIX stuff for get_dpart() */
#define ASLASH		case '/'
#define GOTTA_EVAL	case '?': case '\\': case '[': case '*'
#endif	/* ATARI */


#endif	/* UX_OS || ATARI */







   /* Get the next part of the filename (ie the stuff between "/"s).  The
    *   parts of "/foo/*.c" are: "/", "foo", "*.c".
    * Input:
    * Output:
    *   eval:  TRUE if part contains wildcards needing expansion.
    *   word:  The part.
    *   start: Points after the part (after the "/" or '\0').
    * Returns:
    *   TRUE: Hit the end of the filename else FALSE.
    */
static int get_dpart(start,word,eval) char **start, *word; int *eval;
{
  register char *ptr = *start;
  int atend = TRUE;

  *eval = FALSE;
  while (TRUE)
  {
    switch (*word++ = *ptr++)
    {
      ASLASH:
        atend = FALSE;
        if (*eval) word--;			/* remove trailing "/" */
      case '\0': *word = '\0'; *start = ptr; return atend;
      GOTTA_EVAL: *eval = TRUE; break;
    }
  }
}


#ifdef TEST
/* **************** TEST ******************************************** */

#include "dtable.h"
declare_and_init_dTable(ntable,char *);

extern char *savestr();

add_to_table(name) char *name;
{
  static int n = 0;

  xpand_dTable(&ntable, 1, 50, 25);
  ntable.table[n++] = savestr(name);
  return 0;
}

char name_heap[3000];
main()
{
  char buf[80], *zim = NULL;
  int j, nopath;

  printf("Use name_heap? "); gets(buf); if (atoi(buf)) zim = name_heap;
  printf("No path? "); gets(buf); nopath = atoi(buf);

  printf(": "); gets(buf);
  if (fxpand(buf,FALSE,nopath,zim,add_to_table)) puts("blew up");
  else
  {
    if (zim) printf(">%s<\n",zim);
    else
    {
      for (j = 0; j < sizeof_dTable(&ntable); j++)
	printf("table[%d]: %s\n",j,ntable.table[j]);
    }
  }
}
#endif
