/* vtree
  
   +=======================================+
   | This program is in the public domain. |
   +=======================================+
  
   This program shows the directory structure of a filesystem or 
   part of one.  It also shows the amount of space taken up by files
   in each subdirectory. 
  
   Call via
  
	vtree fn1 fn2 fn3 ...
  
   If any of the given filenames is a directory (the usual case),
   vtree will recursively descend into it, and the output line will
   reflect the accumulated totals for all files in the directory.
   
   This program is based upon "agef" written by David S. Hayes at the 
   Army Artificial Intelligence Center at the Pentagon.
   
   This program is dependent upon the new directory routines written by
   Douglas A. Gwyn at the US Army Ballistic Research Laboratory at the
   Aberdeen Proving Ground in Maryland.
*/

#include "patchlevel.h"

#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdio.h>

#include "customize.h"
#include "hash.h"

#define SAME		0	/* for strcmp */
#define BLOCKSIZE	512	/* size of a disk block */

#define K(x)		((x + 1023)/1024)	/* convert stat(2) blocks into
					 * k's.  On my machine, a block
					 * is 512 bytes. */

#define	TRUE	1
#define	FALSE	0
#define	V_CHAR	"|"	/*	Vertical character	*/
#define	H_CHAR	"-"	/*	Horizontal character	*/
#define	A_CHAR	">"	/*	Arrow char		*/
#define	T_CHAR	"+"	/*	Tee char		*/
#define	L_CHAR	"\\"	/*	L char, bottom of a branch	*/

#define	MAX_COL_WIDTH	15
#define	MAX_V_DEPTH	256		/* max depth for visual display */

int		indent = 0,		/* current indent */
		depth = 9999,		/* max depth */
		cur_depth = 0,	
		sum = FALSE,		/* sum the subdirectories */
		dup = FALSE,		/* use duplicate inodes */
		cnt_inodes = FALSE,	/* count inodes */
		quick = FALSE;		/* quick display */
		visual = FALSE;		/* visual display */
		version = 0;		/* = 1 display version, = 2 show options */
		sub_dirs[MAX_V_DEPTH];

struct	stat	stb;			/* Normally not a good idea, but */
					/* this structure is used through- */
					/* out the program		   */

extern char    *optarg;			/* from getopt(3) */
extern int      optind,
                opterr;


char           *Program;		/* our name */
short           sw_follow_links = 1;	/* follow symbolic links */
short           sw_summary;		/* print Grand Total line */

int             total_inodes, inodes;	/* inode count */
long            total_sizes, sizes;	/* block count */

char            topdir[NAMELEN];	/* our starting directory */


 /*
  * We ran into a subdirectory.  Go down into it, and read everything
  * in there. 
  */
int	indented = FALSE;	/* These had to be global since they */
int	last_indent = 0;	/* determine what gets displayed during */
				/* the visual display */

down(subdir)
char	*subdir;
{
OPEN	*dp;			/* stream from a directory */
OPEN	*opendir ();
char	cwd[NAMELEN];
READ	*file;			/* directory entry */
READ	*readdir ();
int	i;
struct	stat	stb;

	if ( (cur_depth == depth) && (!sum) )
		return;

/* display the tree */

	if (cur_depth < depth) {
		if (visual) {
			if (!indented) {
				for (i = 1; i <cur_depth; i++) {
					if (sub_dirs[i]) {
						printf("%*s%s   ",MAX_COL_WIDTH-4," ",V_CHAR);
					}
					else printf("%*s   ",MAX_COL_WIDTH-3," ");
				}
				if (cur_depth>0) {
					if (sub_dirs[cur_depth] == 0) {
						printf("%*s%s%s%s ",MAX_COL_WIDTH-4," ",L_CHAR,H_CHAR,A_CHAR);
					}
					else printf("%*s%s%s%s ",MAX_COL_WIDTH-4," ",T_CHAR,H_CHAR,A_CHAR);
				}
			} else {
				for (i = 1; i<MAX_COL_WIDTH-last_indent-3; i++)
					printf("%s",H_CHAR);
				printf("%s%s%s ",T_CHAR,H_CHAR,A_CHAR);
			}

/* This will only happen on the first entry into this routine */

			if (strlen(subdir) > MAX_COL_WIDTH - 4) {
				printf("%s\n",subdir);
				printf("%s ",&subdir[strlen(subdir)-MAX_COL_WIDTH+5]);
				last_indent = MAX_COL_WIDTH - 4;
			}
			else {
				printf("%s ",subdir);
				last_indent = strlen(subdir)+1;
			}
			indented = TRUE;
		}
		else printf("%*s%s",indent," ",subdir);
	}

/* open subdirectory */

	if ((dp = opendir(subdir)) == NULL) {
		printf(" - can't read %s\n", subdir);
		indented = FALSE;
		return;
	}

	cur_depth++;
	indent+=3;

	getcwd(cwd, sizeof(cwd));		/* remember where we are */
	chdir(subdir);				/* go into subdir */
	if ( (!quick) && (!visual) ) {

/* accumulate total sizes and inodes in current directory */

		for (file = readdir(dp); file != NULL; file = readdir(dp))
			if (strcmp(NAME(*file), "..") != SAME) 
				get_data(NAME(*file),FALSE);

		if (cur_depth<depth) {
			if (cnt_inodes) printf("   %d",inodes);
			printf(" : %ld\n",sizes);
			total_sizes += sizes;
			total_inodes += inodes;
			sizes = 0;
			inodes = 0;
		}
		rewinddir(dp);
	} else if (!visual) printf("\n");

	if (visual) {

/* count subdirectories */

		for (file = readdir(dp); file != NULL; file = readdir(dp)) {
			if ( (strcmp(NAME(*file), "..") != SAME) &&
			     (strcmp(NAME(*file), ".") != SAME) ) {
				if (chk_4_dir(NAME(*file))) {
					sub_dirs[cur_depth]++;
				}
			}
		}
		rewinddir(dp);
	}
	
/* go down into the subdirectory */

	for (file = readdir(dp); file != NULL; file = readdir(dp)) {
		if ( (strcmp(NAME(*file), "..") != SAME) &&
		     (strcmp(NAME(*file), ".") != SAME) ) {
			if (chk_4_dir(NAME(*file))) 
				sub_dirs[cur_depth]--;
			get_data(NAME(*file),TRUE);
		     }
	}

	if ( (!quick) && (!visual) ) {

/* print totals */

		if (cur_depth == depth) {
			if (cnt_inodes) printf("   %d",inodes);
			printf(" : %ld\n",sizes);
			total_sizes += sizes;
			total_inodes += inodes;
			sizes = 0;
			inodes = 0;
		}
	} else if (visual && indented) {
		printf("\n");
		indented = FALSE;
	}

	indent-=3;
	sub_dirs[cur_depth] = 0;
	cur_depth--;

	chdir(cwd);			/* go back where we were */
	closedir(dp);			/* shut down the directory */
} /* down */



int	chk_4_dir(path)
char	*path;
{
	if (is_directory(path)) return TRUE;
	else return FALSE;
		
} /* chk_4_dir */



/* Is the specified path a directory ? */

int	is_directory(path)
char           *path;
{

#ifdef LSTAT
	if (sw_follow_links)
		stat(path, &stb);	/* follows symbolic links */
	else
		lstat(path, &stb);	/* doesn't follow symbolic links */
#else
	stat(path, &stb);
#endif

	if ((stb.st_mode & S_IFMT) == S_IFDIR)
		return TRUE;
	else return FALSE;
} /* is_directory */



 /*
  * Get the aged data on a file whose name is given.  If the file is a
  * directory, go down into it, and get the data from all files inside. 
  */

get_data(path,cont)
char           *path;
int		cont;    
{
/* struct	stat	stb; */
int		i;

	if (cont) { 
		if (is_directory(path)) 
			down(path);
	}
	else {
		if (is_directory(path)) return;

		    /* Don't do it again if we've already done it once. */

		if ( (h_enter(stb.st_dev, stb.st_ino) == OLD) && (!dup) )
			return;
		inodes++;
		sizes+= K(stb.st_size);
	}
} /* get_data */



main(argc, argv)
int	argc;
char	*argv[];
{
int	i,
	j,
	err = FALSE;
int	option;
int	user_file_list_supplied = 0;

	Program = *argv;		/* save our name for error messages */

    /* Pick up options from command line */

	while ((option = getopt(argc, argv, "dh:istqvV")) != EOF) {
		switch (option) {
			case 'h':	depth = atoi(optarg);
					while (*optarg) {
						if (!isdigit(*optarg)) {
							err = TRUE;
							break;
						}
						optarg++;
					}
					break;
			case 'd':	dup = TRUE;
					break;	
			case 'i':	cnt_inodes = TRUE;
					break;		
			case 's':	sum = TRUE;
					break;
			case 't':	sw_summary = TRUE;
					break;
			case 'q':	quick = TRUE;
					dup = FALSE;
					sum = FALSE;
					cnt_inodes = FALSE;
					break;
			case 'v':	visual = TRUE;
					break;
			case 'V':	version++;
					break;
			default:	err = TRUE;
		}
		if (err) {
			fprintf(stderr,"%s: [ -d ] [ -h # ] [ -i ] [ -s ] [ -q ] [ -v ] [ -V ]\n",Program);
			fprintf(stderr,"	-d	count duplicate inodes\n");
			fprintf(stderr,"	-h #	height of tree to look at\n");
			fprintf(stderr,"	-i	count inodes\n");
			fprintf(stderr,"	-s	include subdirectories not shown due to -h option\n");
			fprintf(stderr,"	-t	totals at the end\n");
			fprintf(stderr,"	-q	quick display, no counts\n");
			fprintf(stderr,"	-v	visual display\n");
			fprintf(stderr,"	-V	show current version\n");
			fprintf(stderr,"		(2 Vs shows specified options)\n");
			exit(-1);
		}
	
	}

	if (version > 0 ) {
		printf("%s",VERSION);
		if (version>1) {
			printf("Tree height:	%d\n",depth);
			if (dup) printf("Include duplicate inodes\n");
			if (cnt_inodes) printf("Count inodes\n");
			if (sum) printf("Include unseen subdirectories in totals\n");
			if (sw_summary) printf("Print totals at end\n");
			if (quick) printf("Quick display only\n");
			if (visual) printf("Visual tree\n");
		}
	}
	
    /* If user didn't specify targets, inspect current directory. */

	if (optind >= argc) {
		user_file_list_supplied = 0;
	} else {
		user_file_list_supplied = 1;
	}

	getcwd(topdir, sizeof (topdir));		/* find out where we are */

    /* Zero out grand totals */
	total_inodes = total_sizes = 0;
    /* Zero out sub_dirs */
	for (i=0; i<=MAX_V_DEPTH; i++) {
		sub_dirs[i] = 0;
	}
		
    /* Inspect each argument */
	for (i = optind; i < argc || (!user_file_list_supplied && i == argc); i++) {
		cur_depth = inodes = sizes = 0;

		chdir(topdir);		/* be sure to start from the same place */
		get_data(user_file_list_supplied?argv[i] : topdir, TRUE);/* this may change our cwd */

		total_inodes += inodes;
		total_sizes += sizes;
	}

	if (sw_summary) {
		printf("\n\nTotal space used: %ld\n",total_sizes);
		if (cnt_inodes) printf("Total inodes: %d\n",inodes);
	}
	
#ifdef HSTATS
	fflush(stdout);
	h_stats();
#endif

	exit(0);
} /* main */


