/* @(#) dusort.c 1.7 93/08/18 00:09:05
 *
 * disk usage sorter
 *
 * Copyright 1990-1992, Unicom Systems Development.  All rights reserved.
 * See accompanying README file for terms of distribution and use.
 *
 * Edit at tabstops=4.
 *
 * This filter reads the output from "du", builds a tree representing
 * the directories scanned, sorts the nodes in descending order, and
 * traverses the tree, thus yielding a printout of disk usage sorted
 * within directories.
 */

#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#define USAGE		"usage: %s [-t threshold] [file]\n"
#define INDENT		4
#define LISTINCR	16
#define TRUE		1
#define FALSE		0

/*
 * The disk usage information is loaded into a tree structure.  Each node
 * of the tree is a (struct duentry), and corresponds to one component
 * in a pathname.  If the node represents a directory, its contents (both
 * files and subdirectories) will be stored in the "child[]" list.  The
 * disk usage at this entry will be saved into "size", or set to -1 if
 * no disk usage info exists at this entry.  Only those entries with a
 * nonnegative "size" value will be printed in the final report.
 */
struct duentry {
	char *name;				/* pathname component at this node			*/
	long size;				/* usage size info, or -1 if not available	*/
	struct duentry **child;	/* directory contents						*/
	int ch_alloc;			/* allocated size of "child" array			*/
	int ch_size;			/* number of elements used in "child" array	*/
	short leading_slash;	/* is there a "/" before the name component	*/
};

/*
 * Only save off entries whose disk usage exceeds this threshold.
 */
long Threshold;


struct duentry *make_entry();	/* allocate a node to be placed in the tree	*/
struct duentry *find_entry();	/* search directory for an entry			*/
struct duentry *attach_entry();	/* add entry as child to current directory	*/
void sort_tree();				/* sort tree in descending order			*/
void print_tree();				/* pre-order traversal of the tree			*/
char *xstrdup();
PTRTYPE *xmalloc();
PTRTYPE *xrealloc();


main(argc, argv)
int argc;
char *argv[];
{
	char buf[1024], *fname, *last_field, *bufp, *s;
	int lineno, leading_slash, i;
	long size;
	struct duentry *du_root, *p, *p1;
	extern int optind;
	extern char *optarg;

	/*
	 * Initialize.
	 */
	Threshold = 0L;
	du_root = make_entry("", FALSE);

	/*
	 * Crack command line.
	 */
	while ((i = getopt(argc, argv, "t:")) != EOF) {
		switch (i) {
		case 't':
			if ((Threshold=atol(optarg)) <= 0L && strcmp(optarg, "0") != 0) {
				fprintf(stderr, "%s: bad threshold value \"%s\"\n",
					argv[0], optarg);
				exit(1);
			}
			break;
		default:
			fprintf(stderr, USAGE, argv[0]);
			exit(1);
		}
	}

	/*
	 * Figure out where input is coming from.
	 */
	switch (argc-optind) {
	case 1:
		fname = argv[optind];
		if (freopen(fname, "r", stdin) == NULL) {
			perror(fname);
			exit(1);
		}
		break;
	case 0:
		/* read from stdin */
		fname = "<stdin>";
		break;
	default:
		fprintf(stderr, USAGE, argv[0]);
		exit(1);
	}

	/*
	 * Read in the input data.  It is expected to be in a format:
	 *
	 *		size   ....   pathname
	 */
	lineno = 0;
	while (++lineno, fgets(buf, sizeof(buf), stdin) != NULL) {

		/*
		 * Break the line up into fields.  Save the first field as the size.
		 */
		s = NULL;
		bufp = buf;
		i = 0;
		while (last_field = s, (s = strtok(bufp, " \t\n")) != NULL) {
			bufp = NULL;
			if (i++ == 0)
				size = atol(s);
		}
		if (i < 2) {
			fprintf(stderr, "%s(%d): line ignored\n", fname, lineno);
			continue;
		}

		/*
		 * Ignore this entry if it is smaller than what we want.
		 */
		if (size < Threshold)
			continue;

		/*
		 * Break the pathname into components, and descend down
		 * into the tree, creating nodes as required.
		 */
		leading_slash = (*last_field == '/');
		p = du_root;
		while ((s = strtok(last_field, "/")) != NULL) {
			if ((p1 = find_entry(p, s)) == NULL)
				p1 = attach_entry(p, make_entry(s, leading_slash));
			last_field = NULL;
			leading_slash = TRUE;
			p = p1;
		}

		/*
		 * Save disk usage for this entry in last component of the path.
		 */
		p->size = size;

	}

	/*
	 * Sort and traverse the tree.
	 */
	sort_tree(du_root);
	buf[0] = '\0';
	print_tree(du_root, buf, buf, 0);

	exit(0);
	/*NOTREACHED*/
}


/*
 * Allocate a new node for the tree.
 */
struct duentry *make_entry(name, leading_slash)
register char *name;
int leading_slash;
{
	register struct duentry *p;
	p = (struct duentry *) xmalloc(sizeof(struct duentry));
	p->name = xstrdup(name);
	p->size = -1L;
	p->child = (struct duentry **) NULL;
	p->ch_alloc = 0;
	p->ch_size = 0;
	p->leading_slash = (short) leading_slash;
	return p;
}


/*
 * Search a directory entry for a member entry and return a pointer to
 * the located entry.  If not found, add this member to the directory.
 */
struct duentry *find_entry(p, name)
register struct duentry *p;
register char *name;
{
	register int i;

	for (i = 0 ; i < p->ch_size ; ++i) {
		if (strcmp(name, p->child[i]->name) == 0)
			return p->child[i];
	}
	return (struct duentry *) NULL;
}


/*
 * Add a new child at the current node.
 */
struct duentry *attach_entry(p, pchild)
register struct duentry *p, *pchild;
{
	if (p->ch_size >= p->ch_alloc) {
		p->ch_alloc += LISTINCR;
		p->child = (struct duentry **) xrealloc((PTRTYPE *)p->child,
			p->ch_alloc*sizeof(struct duentry *));
	}
	p->child[p->ch_size++] = pchild;
	return pchild;
}


/*
 * Recursively sort the tree so that the nodes in "child[]" are in
 * descending order by disk usage.  This is done as a selection sort.
 */
void sort_tree(p)
struct duentry *p;
{
	int i, j, m;
	long msize;
	struct duentry *t;

	/*
	 * Selection sort.
	 */
	for (i = 0 ; i < p->ch_size ; ++i) {
		/*
		 * INVARIANT: child[0:i-1] is sorted in descending order.
		 * child[x] > child[y] for all x < y && x < i.
		 */
		m = i;
		msize = p->child[i]->size;
		for (j = i+1 ; j < p->ch_size ; ++j) {
			/*
			 * INVARIANT: child[m] is the maximum in the range child[i:j-1]
			 */
			if (p->child[j]->size > msize) {
				m = j;
				msize = p->child[j]->size;
			}
		}
		/*
		 * RESULT: child[m] is the maximum in the range [i:ch_size-1]
		 */
		if (m != i) {
			t = p->child[m];
			p->child[m] = p->child[i];
			p->child[i] = t;
		}
		/*
		 * RESULT: child[0:i] is sorted,
		 * child[x] > child[y] for all x < y && x < i+1.
		 */
		sort_tree(p->child[i]);
	}
}


/*
 * Perform a preorder traversal of the tree to get a sorted report.
 */
void print_tree(p, buf, bufend, lvl)
struct duentry *p;
char *buf, *bufend;
int lvl;
{
	int i;
	char *s;

	/*
	 * Append this entry to the pathname.
	 */
	if (p->leading_slash)
		*bufend++ = '/';
	for (s = p->name ; *s != '\0' ; )
		*bufend++ = *s++;
	*bufend = '\0';

	/*
	 * If there is a size at this entry then display it.
	 */
	if (p->size >= 0) {
		printf("%ld\t%*s%s\n", p->size, INDENT*lvl, "",
			(buf[0] == '\0' ? "/" : buf));
		++lvl;
	}

	/*
	 * Recurse into the children of this entry.
	 */
	for (i = 0 ; i < p->ch_size ; ++i)
		print_tree(p->child[i], buf, bufend, lvl);
}


static char malloc_error[] = "error - out of memory [malloc failed]\n";

char *xstrdup(s)
register char *s;
{
	register char *s1;
	if ((s1 = (char *) malloc((unsigned)strlen(s)+1)) == NULL) {
		fputs(malloc_error, stderr);
		exit(1);
	}
	return strcpy(s1, s);
}

PTRTYPE *xmalloc(n)
register unsigned n;
{
	register PTRTYPE *s;
	if ((s = malloc(n)) == NULL) {
		fputs(malloc_error, stderr);
		exit(1);
	}
	return s;
}

PTRTYPE *xrealloc(s, n)
register PTRTYPE *s;
register unsigned n;
{
	if ((s = (s == NULL ? malloc(n) : realloc(s, n))) == NULL) {
		fputs(malloc_error, stderr);
		exit(1);
	}
	return s;
}

