/*$llib lcm */
#include "dos.h"
#include "stdlib.h"
#include "stdio.h"

/*===========================================================================*\
|| HOW-BIG								     ||
||				     Copyright (C) 1987 Rip Toren	     ||
||							POB 674 	     ||
||							Columbia, MD 21045   ||
||									     ||
||   will display (may be redirected to a file) the sub-directory struct.    ||
|| under the current directory.  For each entry the total size of the	     ||
|| file (in bytes) will be display, followed by the total byte count for     ||
|| all the sub-directories under that one.  This will allow you to tell      ||
|| where (or at lest which branch) is using up most of your disk space.      ||
||     This process is limited to 20 levels of sub-directories, just as      ||
|| a sanity check on the recursion.					     ||
||									     ||
||===========================================================================||
|| HISTORY								     ||
||   5-1-87  Original release  Rip Toren,  Columbia MD			     ||
||									     ||
||===========================================================================||
|| DEVELOPMENT data							     ||
||     The program was compiled with Lattice 'C' 3.1 in a small memory       ||
|| model.  There are a number of DOS specific functions that you may have    ||
|| to supply if they are not part of your compiler:			     ||
||									     ||
||    dfind    - DOS service 0x4E  find first match			     ||
||    dnext    - DOS service 0x4F  find next match			     ||
||		 In Lattice, these only work in current directory ???	     ||
||    chdir    - DOS service 0x3B  change directory			     ||
||    getdsk   - DOS service 0x19  get default drive (a:=0)		     ||
||    getdfs   - DOS service 0x1B  get disk stats			     ||
||									     ||
|| DEBUGING								     ||
||    I have left in all the conditional debugging statements.	You may      ||
||    watch the process take place by defining "DEBUG" in the beginning.     ||
||									     ||
||									     ||
\*===========================================================================*/
/*===========================================================================*\
|| attributes in HEX							     ||
||   01 = read-only	10 = subdirectory				     ||
||   02 = hidden	20 = arcvive bit				     ||
||   04 = system							     ||
||   08 = vol label	all others are zero upon return !		     ||
\*===========================================================================*/

#ifdef DEBUG
#define chdir do_chdir
  char dbug_dir [65];
#endif

   /*------------------------------------------------------------------------*\
   |   storage for disk and file data					      |
   \*------------------------------------------------------------------------*/
struct FILEINFO  fi;		       /* Lattice dfind data		     */
struct DISKINFO  di;		       /* Lattice getdfs data		     */

   /*------------------------------------------------------------------------*\
   |   Allocated for each directory entry in the tree.			      |
   \*------------------------------------------------------------------------*/
struct sub_d  {
   char sd_name [13];		       /* simple name - no slash	     */
   int	child_cnt;		       /* how many sub-dirs under on next lev*/
   long bytes  ;		       /* total current byte count	     */
   long blocks ;		       /* total alloacation unit count	     */
   long sd_bytes ;		       /* total child sub-dir byte count     */
   long sd_blocks;		       /* total sub-dir allocation count     */
   struct sub_d * _parent;	       /* pointer to parent		     */
   struct sub_d * _twin;	       /* pointer to first child of current  */
   struct sub_d * _child;	       /* pointer to twin under same parent  */
   } ;

typedef struct sub_d * sd_ptr;
typedef struct sub_d   sd_member;


   /*------------------------------------------------------------------------*\
   |   Forward definitions						      |
   \*------------------------------------------------------------------------*/
sd_ptr add_twin();
sd_ptr add_child();
sd_ptr new_member();

   /*------------------------------------------------------------------------*\
   |   Other incidental items						      |
   \*------------------------------------------------------------------------*/
    sd_ptr root_ptr;		       /* 1st entry in string		     */
    char   start_point[75];	       /* full name of starting directory    */
    int    child_level ;	       /* count the number of levels down    */
    char   drive [2];		       /* build drive letter		     */
    char   curr_dir [65];	       /* current directory we are in	     */
    unsigned int bytes_per_cluster;

    /*-----------------------------------------------------------------------*\
    |  Lattice way to specify the amout of stack space. Each recursion	      |
    |  cost 81 bytes (each sub-directory)				      |
    \*-----------------------------------------------------------------------*/
int _stack = 8000;

/*===========================================================================*\
||									     ||
\*===========================================================================*/
main ()
  {
   setnbf  (stdout);		       /* stdout not buffered		     */
   /*------------------------------------------------------------------------*\
   |   establish current drive						      |
   \*------------------------------------------------------------------------*/
   drive[0] = getdsk() + 'A';
   drive[1] = 0;
   strcpy (curr_dir,drive);	       /* get the letter		     */
   strcat (curr_dir,":\\");            /* append the :\                      */

   /*------------------------------------------------------------------------*\
   |   insert the first node in the chain				      |
   \*------------------------------------------------------------------------*/
   root_ptr = new_member();	       /* insert member for current dir.     */
   if (getcd  (0,start_point) ==0)  {  /* build full name for curr dir.      */
	strcpy (root_ptr -> sd_name, start_point);
	strins(start_point,":\\");
	strins(start_point,drive);
	}

   /*------------------------------------------------------------------------*\
   |   Set up blocking factor (cluster size)				      |
   \*------------------------------------------------------------------------*/
   getdfs (0,&di);
   bytes_per_cluster = (di.bps * di.spc);
/*
   printf ("Clusters per drive : %d\n",di.cpd);
   printf ("Sectors per cluster: %d\n",di.spc);
   printf ("Bytes per sector   : %d\n",di.bps);
   printf ("Free clusters      : %d\n\n",di.free);
*/
   child_level =0;
   proc_dir (root_ptr); 	       /* extract its byte count & sub-dirs. */
   chdir (start_point); 	       /* be sure back in starting dir.      */
   list_chain (root_ptr, "");          /* display the results                */
   }

/*===========================================================================*\
|| Recursive procedure to look through each directory			     ||
|| Accumulate the byte and block counts.  Add any newly found sub-dirs to    ||
|| the chains.	Then do a 'depth-first' search down the child chain, folowed ||
|| by a look at the next twin on this level.				     ||
\*===========================================================================*/
proc_dir(here)
sd_ptr here;
  {
   int s;			       /* status code			     */
   sd_ptr h1;			       /* temp holder for the pointer	     */
   char my_dir[65];		       /* the location of this directory     */

   strcpy (my_dir, curr_dir);
   getcd (0,&my_dir[3]);	       /* full path after the drive:\	     */

#ifdef DEBUG
   printf ("PROC_DIR processing [%-13s], really <%s>\n",here -> sd_name,my_dir);
#endif

   /*------------------------------------------------------------------------*\
   |   locate first file in directory					      |
   \*------------------------------------------------------------------------*/
   s = dfind (&fi, "*.*",0xff);
   if (s != 0)	{		       /* ERROR 			     */
       printf (" s= %d name = %s\n", s, here -> sd_name);
       return;
       }

   /*------------------------------------------------------------------------*\
   |   Now get the rest of the files					      |
   \*------------------------------------------------------------------------*/
   while (s == 0) {		       /* while there are more entries	     */
				       /* total up byte count		     */
       if (fi.attr != 0x10)  {	       /* a file !!			     */
	   here -> bytes += fi.size;
	   if (fi.size > 0 )
	       here -> blocks += (fi.size / bytes_per_cluster) + 1;
	   }
       else  {			       /* sub-directory 		     */
	   if (fi.name[0] != '.')  {   /* not self-references or parent      */

#ifdef DEBUG
   printf ("    Add [%s] to [%s] as child\n",fi.name,here -> sd_name);
#endif
	       h1 = add_child (here);  /* add sub-dir. under 'here'          */
				       /* insert the name of the sub-dir.    */
	       strcpy(h1 -> sd_name,fi.name);
	       }
	   }
       s = dnext (&fi); 	       /* and on to the next !		     */
       }

   /*------------------------------------------------------------------------*\
   |   Have accumulated all space in this dir & sub-directories 	      |
   \*------------------------------------------------------------------------*/


   /*------------------------------------------------------------------------*\
   |   now look at the child directories				      |
   \*------------------------------------------------------------------------*/
#ifdef DEBUG
   getcd(0,dbug_dir);
   printf ("    PROC_DIR [%-13s] looking for child  cd=<%s>\n",
	    here -> sd_name,dbug_dir);
#endif

   if (here -> _child != NULL)	{      /* Has sub-dirs. 		     */
       if (child_level++ > 20)	{      /* check for too much depth	     */
	   puts ("PROC_DIR (recursive error ?) child_level too deep");
	   exit(-1);
	   }
				       /* move down to the child	     */
       chdir (here -> _child -> sd_name);

#ifdef DEBUG
   printf ("    child = %s\n",here -> _child -> sd_name);
#endif
       proc_dir ( here -> _child );    /* proc single child		     */
       }

   /*------------------------------------------------------------------------*\
   |   Now check for twins						      |
   |   a twin is the second or subsequent child of the parent		      |
   |									      |
   \*------------------------------------------------------------------------*/
#ifdef DEBUG
   getcd(0,dbug_dir);
   printf ("    PROC_DIR [%-13s] looking for twin   cd=<%s>\n",
	    here -> sd_name,dbug_dir);
#endif

   if (here -> _twin != NULL)  {       /* no twins			     */
       chdir ("..");                   /* back to parent                     */
				       /* down to twin			     */
#ifdef DEBUG
   printf ("    twin  = %s\n",here -> _twin -> sd_name);
#endif
       chdir (here -> _twin  -> sd_name);

       proc_dir ( here -> _twin );     /* proc single twin		     */


       /*--------------------------------------------------------------------*\
       |   Now reset to 'my_dir' to keep things current, just in case         |
       |   since the twin did not know the name of the sub-dir prior.	      |
       \*--------------------------------------------------------------------*/
       chdir (my_dir);
       }

   /*------------------------------------------------------------------------*\
   |   NOW UPDATE COUNTS IN THE PARENT					      |
   \*------------------------------------------------------------------------*/
   here -> _parent -> sd_bytes += (here -> bytes) + (here -> sd_bytes);
   here -> _parent -> sd_blocks+= (here -> blocks) + (here -> sd_blocks);

   chdir ("..");                       /* back to parent                     */
   child_level --;		       /* back up a level		     */
   return;
   }

/*===========================================================================*\
|| ADD_CHILD								     ||
||    add a child record under the given parent.  If the parent 	     ||
|| Return a pointer to the new allocation, so that the name may be filled in ||
||									     ||
\*===========================================================================*/
sd_ptr
add_child (parent)
sd_ptr parent;
  {
   sd_ptr  new_child;

#ifdef DEBUG
   printf ("    ADD_CHILD to %s\n",parent -> sd_name);
#endif

   if (parent -> _child != NULL)  {    /* already has one		     */

#ifdef DEBUG
   printf ("    already has child, add a twin\n");
#endif

       parent -> child_cnt ++;
       return (add_twin (parent -> _child));
       }
   new_child = new_member();	       /* allocate space		     */
   new_child -> _parent = parent;      /* set back pointer		     */
   parent -> _child = new_child;       /* set parent pointer		     */
   parent -> child_cnt ++;	       /* need this for graphic display      */
   return (new_child);		       /* return allocation pointer	     */
   }
/*===========================================================================*\
|| ADD_TWIN								     ||
||     Allocate a new member and link it as a twin to the entry passed.      ||
||									     ||
\*===========================================================================*/
sd_ptr
add_twin (sibling)
sd_ptr sibling;
  {
   sd_ptr sib_ptr;

#ifdef DEBUG
   printf ("    ADD_TWIN  to %s\n",sibling-> sd_name);
#endif
				       /* search twin chain		     */
   for (sib_ptr = sibling;
	sib_ptr -> _twin != NULL;
	sib_ptr = sib_ptr -> _twin);

   sib_ptr -> _twin = new_member();    /* allocate it & add to chain	     */
				       /* set parent pointer		     */
   sib_ptr -> _twin -> _parent = sibling -> _parent;
   return (sib_ptr -> _twin) ;	       /* return allocation pointer	     */
   }

/*===========================================================================*\
|| NEW_MEMBER								     ||
||     Allocate and init a new member for someone.			     ||
\*===========================================================================*/
sd_ptr
new_member ()
  {
   sd_ptr nm;

#ifdef DEBUG
   printf ("       .... NEW_MEMBER\n");
#endif
   nm = (sd_ptr) calloc (sizeof (sd_member),1);
   if (nm == NULL)  {
       puts ("Error during new member allocation");
       exit (-1);
       }
   nm -> _parent = NULL;
   nm -> _twin	 = NULL;
   nm -> _child  = NULL;
   return (nm);
   }


/*===========================================================================*\
|| LIST_CHAIN								     ||
||     Recursive process to list out the items in the list.  The 'offset'    ||
|| supplied is the grapgic string to place in from of this entry when	     ||
|| it is printed.							     ||
||									     ||
\*===========================================================================*/
list_chain (sdpt,offset)
sd_ptr	sdpt;
char *offset;
  {
   char my_offset [100];
   char * direct_name;
   int lastm;
   float perc1, perc2;
   long  space1, space2;

   strcpy (my_offset,offset);	       /* make my copy to pass along	     */

   /*------------------------------------------------------------------------*\
   |  'lastm' is the length of the offset -2. On the initial entry, a value   |
   |   <0 indicates the first entry.  On all others, it is the symbols that   |
   |   weere used on the last level.  These need to be adjusted for	      |
   |   continueing vertical line between sub-dirs on higher levels.	      |
   \*------------------------------------------------------------------------*/
   lastm=strlen(offset)-2;	       /* figure outif outer values must chng*/
   if (lastm < 0)  {		       /* Top level only		     */
       puts (" This Directory  (All Sub-Directories)");
       puts (" dsk spc slack   dsk spc  slack ");
       }

   do  {
				       /* is this the top? use start point   */
       if (sdpt != root_ptr) direct_name = sdpt -> sd_name;
	   else 	     direct_name = start_point;

				       /* get rid of tail on last child line */
       if (sdpt -> _parent -> child_cnt == 1)
	  my_offset [lastm] = 0xc0;

       /*--------------------------------------------------------------------*\
       |   Compute the slack percentage 				      |
       \*--------------------------------------------------------------------*/
       space1 = (sdpt ->    blocks * bytes_per_cluster) - sdpt -> bytes;
       if (space1 > 0)
	 perc1 = ((float)space1 / (float)(sdpt -> blocks * bytes_per_cluster)) * 100.0;
	else perc1 = 0;

       space2 = (sdpt -> sd_blocks * bytes_per_cluster) - sdpt -> sd_bytes;
       if (space2 > 0)
	 perc2 = ((float)space2 / (float)(sdpt -> sd_blocks * bytes_per_cluster) ) * 100.0;
	else perc2 = 0;
       /*--------------------------------------------------------------------*\
       |   Print the line itself.					      |
       \*--------------------------------------------------------------------*/
				       /* print the entry		     */
       printf ("%6ldk  %4.1f%% (%6ldk  %4.1f%%)  %s%-s\n",
		(long) ((sdpt -> blocks * bytes_per_cluster) / 1024),
		perc1,
		(long) ((sdpt -> sd_blocks * bytes_per_cluster) / 1024),
		perc2,
		my_offset,direct_name);

       sdpt -> _parent -> child_cnt --;/* one less child to print	     */
       if (sdpt -> child_cnt > 0)  {   /* this entry has children	     */
	   /*----------------------------------------------------------------*\
	   |   adjust display above this level				      |
	   \*----------------------------------------------------------------*/
	   if (lastm >= 0)  {	       /* below sdpt level		     */
	       if (my_offset[lastm] == 0xc3) my_offset[lastm] = 0xb3;
		  else			     my_offset[lastm] = ' ';
	       my_offset[lastm+1] = ' ';
	       }
	   /*----------------------------------------------------------------*\
	   |   add new character to offset for next level		      |
	   \*----------------------------------------------------------------*/
	   if (lastm >= 0)  {
	       if (sdpt -> child_cnt > 1) strcat (my_offset,"\xc3\xc4");
		  else			  strcat (my_offset,"\xc0\xc4");
	       }
	    else
	       strcat (my_offset,"\xc3\xc4");

	   list_chain (sdpt -> _child,my_offset);
	   strcpy (my_offset,offset);  /* make my copy to pass along	     */
	   }
       sdpt = sdpt -> _twin;	       /* process the next on same level     */
       } while (sdpt != NULL);	       /* while there are some		     */

   return;
   }

#ifdef DEBUG

#undef	chdir

do_chdir(s)

char *s;
  {
   char pdir[60];
   getcd (0,pdir);
   printf (">   FROM [%s] -> [%s]  = ", pdir,s);
   chdir (s);
   getcd (0,pdir);
   printf ("<%s>\n",pdir);
   }
#endif
