/*
** TREE
**  Gives a recursive listing of files in the given directories.
**
**     options are:
**            -<n> = only to level 'n'
**            -s   = show sizes of files
**            -l   = long format
**            -f   = use full pathnames (not yet implemented)
**            -i   = indent to show directory structure
**            +d   = only show files with "d" attribute set (i.e., dirs)
**            -d   = only show files lacking "d" attribute (i.e., files)
**
**  Created 1992 by Tim Kientzle, based on
**  "tree" for OS9/6809: copyright 1984 by Carl R. Kreider
**        released to the public domain for non-commercial use
*/
 
#undef  DEBUG
 
#include <modes.h>
#include <direct.h>
#include <sgstat.h>
#include <strings.h>
#include <stdio.h>
#include <ctype.h>
#include <dir.h>
 
struct fildesc {
     char      fd_att;
     char      fd_own[2];
     char      fd_date[5];
     char      fd_link;
     char      fd_fsize[4];
     char      fd_dcr[3];
} ;
 
/* Options: Display format */
int    longDisplay = 0;      /* Like dir's "-e" output */
int    sizeDisplay = 0;      /* sizes for each file */
int    fullPathDisplay = 0;  /* full pathnames */
int    indent = 0;           /* indent rather than showing path */
 
/* Options: File selection */
int    attributeMask = 0x00;  /* Set bits are bits that are checked */
int    attributeFlip = 0x00;  /* Set bits here that must be clear in result */
 
/* Special variables */
int    uid;                   /* User ID */
int    rawDevicePath;         /* input device path */
long   sectorSize;            /* media sector size */
 
/* Maximum depth we will consider before aborting */
/* There must be some limit in case the file structure is damaged, */
/* which could otherwise lead to an infinite recursion. */
#define MAX_DEPTH 99
 
/* Filename buffer */
#define  MAX_FILE_NAME   512
char fileName[MAX_FILE_NAME];
 
 
/*page*/
 
/*
** convert bytes into an integer
*/
 
long BytesToInt( bytes, numbytes)
char *bytes;
int numbytes;
{
   int  j;
   long result = 0;
 
   for (j = numbytes; j ; j--)
   {
       result = result * 256 + ( (unsigned long)*bytes++ & 0x00FF);
   }
   return result;
}
 
/*
   Convert a byte into an attribute string for printing
*/
 
char attrBuff[20];
 
char *convert_attr( attr )
int attr;
{
   int nextBit = 0x80;
   char *s2 = attrBuff;
 
   strcpy(s2, "dsewrewr");
   for (nextBit = 0x80; nextBit > 0 ; nextBit >>= 1)
   {
       if (attr & nextBit)
          s2++;
       else
          *s2++ = '-';
   }
   return(attrBuff);
}
 
 
/*
** write the two strings passed and then a <cr>
*/
 
error(s1, s2)
char  *s1, *s2;
{
   fprintf(stderr, "%s %s \n",s1,s2);
}
 
/*
** say that we cant open a file
*/
 
cant(s1)
char  *s1;
{
   error("can't open ",s1);
}
 
 
excant(s1)
char  *s1;
{
   cant(s1);
   exit (0);
}
 
tabulate(n)
int n;
{
  while (n-- > 0)
  {
     printf("    ");
  }
}
 
/*
** provide usage info for this command
*/
 
/*
    Rationale: The +d/-d options have a deeper significance.  "+d" is
    intended to signify that the directory attribute must be set,
    "-d" is intended to signify that the directory attribute must
    be clear.  This could easily be extended to include the other
    attributes.  "-s" is already taken, and "-e" would conflict with
    it's usage in "dir", which create problems.
*/
help()
{
   fprintf(stderr,"Usage: tree [options] [directory]\n");
   fprintf(stderr,"    -<number> = only number levels of the tree (1..9)\n");
   fprintf(stderr,"    -l        = long output format\n");
   fprintf(stderr,"    -s        = show sizes\n");
/*   fprintf(stderr,"    -f        = use full pathnames\n"); */
   fprintf(stderr,"    -i        = indent to show directory structure\n");
   fprintf(stderr,"    +d        = only show directories\n");
   fprintf(stderr,"    -d        = only show non-directories\n");
   exit (0);
}
 
/*page*/
/*
**  return in 'buf' the device name where
**   file 's' resides.
*/
GetDeviceName(dirName, buf)
char  *dirName,   *buf;
{
   char  temp[32];
   int   pn;
 
   /* Handle an omitted directory name with a nice default */
   if (strlen(dirName)==0)
         dirName = ".";
 
   if ((pn = open(dirName, S_IFDIR + S_IREAD)) == -1)
         excant(dirName);
   _gs_devn(pn,temp);
   *buf++ = '/';
   strhcpy(buf, temp);                       /* now copy first item */
   strcat(buf, "@");                                 /* and set raw */
   close(pn);
}
 
 
/*
** seek to 'pos' on device 'pn' and read
**  'fildes' bytes into 'buf'
*/
get_fd(pn, buf, pos)
int   pn;
struct fildesc  *buf;
long  pos;
{
   extern errno;
}
 
 
/*page*/
/*
** snatch memory from the system and read in directory,
**  saving only good files.  Then sort into order.
**
** returns  -1 on memory error
**           0 on open fail (not dir)
**         pnt on success
**         end via reference
*/
 
struct dirent *
CacheDirectory(dirName, end, dirSize)
char  *dirName;
struct dirent  **end;
long dirSize;
{
   struct dirent   entryBuffer;                /* single dir ent read buf */
   struct fildesc  directoryFD;
   struct dirent  *directoryBuffer;            /* stores entire directory */
   struct dirent  *currEntry;                  /* current entry in directoryBuffer */
   int             dirPath;
   int             numEntries = 0;
 
   if (strlen(dirName)==0)
         dirName = ".";
 
   /* Temporarily set our UID back to the user's so that normal */
   /* directory security will apply */
   setuid(uid);
   if ((dirPath = open(dirName, S_IFDIR + S_IREAD)) == -1)
   {
         fprintf(stderr, "Couldn't read directory ``%s''\n",fileName);
         return (-1);                                    /* not dir */
   }
   setuid(0);
 
   /* If we don't already know the directory size, find it the slow way */
   if (dirSize == 0)
   {
       _gs_gfd(dirPath,&directoryFD,sizeof(directoryFD));        /* 'from' filedesc  */
       dirSize = BytesToInt(directoryFD.fd_fsize,4);
   }
 
   /* get memory to store array of directory entries */
   directoryBuffer = (struct dirent *)malloc(dirSize);
   if ((int)(directoryBuffer) == -1)
   {
         fprintf(stderr, "Couldn't allocate memory to store directory entries.\n");
         return(-1);
   }
 
   /* Start at first entry in buffer */
   currEntry = directoryBuffer;
 
   /* Read each entry from directory, and copy them into the buffer */
   while(read(dirPath, &entryBuffer, sizeof(entryBuffer)))       /* now copy names */
   {
      /* Ignore entries that are marked as deleted */
      if (isprint(entryBuffer.dir_name[0] & 0x7f))
      {
          /* Copy directory name over, converting to C format string */
          strhcpy(currEntry->dir_name, entryBuffer.dir_name);
          currEntry->dir_name[27] = '\0';
          currEntry->dir_addr = entryBuffer.dir_addr;
 
          /* don't cache "." and ".." entries */
          if (strcmp(currEntry->dir_name,".") && strcmp(currEntry->dir_name,".."))
          {
              ++currEntry;                           /* point to next spot */
              ++numEntries;
          }
      }
   }
   close(dirPath);
   qsort(directoryBuffer, numEntries, sizeof(*directoryBuffer), strcmp);        
   /* now sort */
   *end = currEntry;
   return (directoryBuffer);
}
 
 
/*page*/
ListDirectory(fileName, maxDepth, currDepth, dirSize)
char *fileName;
int   maxDepth, currDepth;
long  dirSize;
{
   struct fildesc  currentFileDesc;
   struct dirent  *dirBuf, *dirBufEnd, *currentEntry;
   int    fileNameLength = strlen(fileName);
 
#ifdef DEBUG
   printf("ListDirectory( fileName:``%s'', maxDepth:%d, currDepth:%d, dirSize:%ld)\n",
          fileName,maxDepth,currDepth,dirSize);
#endif
 
   /* This helps avoid an infinite recursion caused by */
   /* a damaged file structure. */
   if (currDepth > MAX_DEPTH)
   {
      fprintf(stderr,"I can't deal with %d directory levels.\n",currDepth);
      exit(1);
   }
 
   /* This prevents us from overrunning our buffer */
   if (fileNameLength > ( MAX_FILE_NAME - MAXNAMLEN - 1 ) )
   {
      fprintf(stderr,"I can't handle names this long.\n",currDepth);
      return(0);
   }
 
   if ((int)(dirBuf = CacheDirectory(fileName, &dirBufEnd, dirSize)) == -1)
   {
         return (0);
   }
 
   for(currentEntry = dirBuf; currentEntry < dirBufEnd; currentEntry++)
   {
      /* Build the full name to print for this file */
      if (fileNameLength > 0)
         strcpy(fileName+fileNameLength,"/");
      else
         fileName[0] = '\0';
      strcat(fileName+fileNameLength, currentEntry->dir_name);
 
      /* seek the raw device to get the file descriptor */
      /* (Much faster than _gs_gfd) */
      lseek(rawDevicePath, currentEntry->dir_addr * sectorSize, 0);
      if ((read(rawDevicePath, &currentFileDesc, sizeof(struct fildesc))) == -1)
      {
           fprintf(stderr,"I can't read the file descriptor for ``%s''.\n",fileName);
           return(-1);
      }
 
      /* Only show files that have the indicated attributes */
      if ( ((currentFileDesc.fd_att & attributeMask)^attributeFlip)==0)
      {
          /* print the information for the file */
          if (longDisplay)
              printf("%3d.%-3d   %02d/%02d/%02d %2d%02d   %s%8X%10ld ",
                  currentFileDesc.fd_own[0], currentFileDesc.fd_own[1],
                  currentFileDesc.fd_date[0], currentFileDesc.fd_date[1],
                  currentFileDesc.fd_date[2],
                  currentFileDesc.fd_date[3], currentFileDesc.fd_date[4],
                  convert_attr(currentFileDesc.fd_att),
                  currentEntry->dir_addr,
                  BytesToInt(currentFileDesc.fd_fsize,4));
          else if (sizeDisplay)
              printf("%9ld ", BytesToInt(currentFileDesc.fd_fsize,4));
 
          if (indent)
          {
              tabulate(currDepth-1);
              printf("%s\n",currentEntry->dir_name);
          }
          else
              printf("%s\n",fileName);
      }
 
      /* If it's a directory, list it */
      if (  ((currentFileDesc.fd_att & S_IFDIR) == S_IFDIR) && (currDepth < maxDepth) )
      {
          if ( ListDirectory(fileName, maxDepth, currDepth + 1,
                        BytesToInt(currentFileDesc.fd_fsize,4)) == -1)
             return(-1);
      }
 
   } /* while */
 
   free(dirBuf);
   return(0);
}
 
/*page*/
 
TreeListDirectory( name, maxDepth)
char *name;
int maxDepth;
{
   int   currDepth= 1;
   char  rawDeviceName[40];
   struct sgbuf rawDeviceDesc;                         /* options for inp path */
 
   strcpy(fileName, name);
 
   /* Open the raw device this is on */
   GetDeviceName(fileName, rawDeviceName);
   if ((rawDevicePath = open(rawDeviceName, S_IREAD)) == -1)
         excant(rawDeviceName);
 
   /* Get the sector size for the media */
   _gs_opt(rawDevicePath, &rawDeviceDesc);
   sectorSize = rawDeviceDesc.sg_sctsiz;
   if (sectorSize == 0)
      sectorSize = 256;
 
   /* Now, list the directory contents */
   ListDirectory(fileName, maxDepth, currDepth, 0L);
 
   /* Close the raw device */
   close(rawDevicePath);
}
 
/*page*/
main(argc,argv)
int   argc;
char  **argv;
{
   char *p;
   char  selector;      /* Character beginning option, either '+' or '-' */
   int   maxDepth = 99;
 
   setuid(0);
 
   while (  (*(p = *++argv) == '-')
          ||(*p == '+')  )
   {
         selector = *p;  /* remember initial character */
         while (*++p)
         {
            switch (*p)
            {
            /* Should watch for longer numbers... */
            case '0' :  case '1' :  case '2' :
            case '3' :  case '4' :  case '5' :
            case '6' :  case '7' :  case '8' :
            case '9' :  maxDepth = (*p - '0');  /* nest level */
                        break;
            case 'L' :
            case 'l' :
            case 'E' :
            case 'e' :  longDisplay = 1;
                        break;
            case 'S' :
            case 's' :  sizeDisplay = 1;
                        break;
            case 'F' :
            case 'f' :  fullPathDisplay = 1;
                        break;
            case 'I' :
            case 'i' :  indent = 1;
                        break;
            case 'D' :
            case 'd' :  attributeMask |= S_IFDIR;  /* watch directory bit */
                        if (selector == '-')
                            attributeFlip &= ~S_IFDIR;/* dir bit must be clear *
/
                        else
                            attributeFlip |=  S_IFDIR;/* dir bit must be set */
                        break;
            case 'H' :
            case 'h' :
            case '?' :  help();
                        exit(0);
                        break;
            default  :  fprintf(stderr,"I don't recognize ``%c'' as an option.\n",*p);
                        exit(0);
                        break;
            }
        }
        --argc;                         /* adjust count for option */
   }
 
   /* List the current dir by default, or else list each directory */
   /* given */
   if (argc < 2)
       TreeListDirectory("",maxDepth);
   else
       while (--argc)
           TreeListDirectory(*argv++,maxDepth);
} 
