#ifndef lint
static char SCCSID[] = "@(#) du.c 1.16 93/08/18 00:09:01";
#endif
static char Copyright[] = "@(#) Copyright 1990-1993, Unicom Systems Development, Inc.  All rights reserved.";

/*
 * "du" enhanced disk usage summary - version 2.
 *
 * Copyright 1990-1993, Unicom Systems Development.  All rights reserved.
 * See accompanying README file for terms of distribution and use.
 *
 * Edit at tabstops=4.
 */

#define USAGE "usage: %s [ options ] [ path ... ]  (try \"-h\" for help)\n"

#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <time.h>
#ifdef TIMEOUT
# include <signal.h>
#endif
#ifdef USE_STDARG
# include <stdarg.h>
#else
# include <varargs.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#define INTERN
#include "du.h"
#include "patchlevel.h"

static void do_help __ARGS((void));
static void set_breakdown __ARGS((char *));
static int get_userid __ARGS((char *));


#ifdef TIMEOUT
/*ARGSUSED*/
void alarm_catcher(n)
int n;
{
	errmssg(ERR_ABORT, 0, "timeout occurred -- giving up");
	/*NOTREACHED*/
}
#endif


main(argc, argv)
int argc;
char *argv[];
{
	int i;
	struct dusage tot_usage, ent_usage;
	extern char *optarg;
	extern int optind;

	Progname = argv[0];

	/* 
	 * Crack command line options.
	 */
	while ((i = getopt(argc, argv, "abB:c:CDdfFhiklLrstuU:")) != EOF) {
		switch (i) {
			case 'a':	Handle_output = PR_EVERYTHING;		break;
			case 'b':   Report_blksize = 1;					break;
			case 'B':   Report_blksize = atoi(optarg);		break;
			case 'c':	set_breakdown(optarg);				break;
			case 'C':	Do_file_counts = TRUE;				break;
#ifdef OBSOLETEOPT
			case 'd':	Do_descend_dirs = FALSE;			break;
#endif
			case 'f':	Handle_filesys = FS_NEVER_CROSS;	break;
			case 'F':	Handle_filesys = FS_LOCAL_ONLY;		break;
			case 'h':	do_help();							exit(0);
			case 'i':	Do_accum_subdirs = FALSE;			break;
			case 'k':   Report_blksize = 1024;				break;
			case 'l':	Handle_links = LK_COUNT_ALL;		break;
			case 'L':	Handle_links = LK_COUNT_AVERAGE;	break;
			case 'r':	Do_print_errors = TRUE;				break;
			case 's':	Handle_output = PR_TOTALS_ONLY;		break;
			case 't':	Do_print_grand_total = TRUE;		break;
			case 'u':	Handle_links = LK_COUNT_NONE;		break;
			case 'U':	Selected_user = get_userid(optarg);	break;
#ifdef DEBUG
			case 'D':	Debug = TRUE;						break;
#endif
			default:	fprintf(stderr, USAGE, Progname);
						exit(1);
		}
	}

#ifdef TIMEOUT
	/*
	 * Install the handler to trap timeouts.
	 */
	(void) signal(SIGALRM, alarm_catcher);
#endif

	/*
	 * Initialize the filesystem information tables.
	 */
	fs_initinfo();

	/*
	 * Get the time so we can do the breakdown of usage by age.
	 */
	(void) time(&Curr_time);

	/*
	 * Go get the usage.
	 */
	if (argc == optind) {
		du_entry(".", &tot_usage);
	} else {
		zero_usage(&tot_usage);
		for (i = optind ; i < argc ; ++i) {
			du_entry(argv[i], &ent_usage);
			add_usage(&tot_usage, &ent_usage);
		}
	}

	if (Do_print_grand_total)
		print_usage("TOTAL", &tot_usage);
	exit(0);
	/*NOTREACHED*/
}


static char *help_text[] = {
	"du - version %V",
	"    %C",
	"Usage:",
	"    %P [ options ] [ path ... ]",
	"Options:",
	"    -a          Report all entries, i.e. files as well as directories.",
	"    -b          Equivalent to \"-B 1\".",
	"    -B n        Report in blocks of \"n\" bytes (default %B).",
	"                  (Use \"0\" to report in native filesystem blocks.)",
	"    -c n,n,...  Breakdown by age, one col for each \"n\" days or older.",
	"    -C          Display file counts as well as disk usage.",
#ifdef OBSOLETEOPT
	"    -d          Do not descend into directories.",
#endif
	"    -f          Do not cross any filesystem mount points.",
	"    -F          Do not cross remote filesystem mount points.",
	"    -h          Display this help message.",
	"    -i          Do not accumulate subdirectory usages into parent dir.",
	"    -k          Equivalent to \"-B 1024\".",
	"    -l          Count multiply linked files each time encountered.",
	"    -L          Average usage of multiply linked files across the links.",
#ifndef PRINT_ERRORS
	"    -r          Print (do not suppress) errors which occur during scan.",
#endif
	"    -s          Only report a total for each argument on command line.",
	"    -t          Report a grand total of all items.",
	"    -u          Skip (do not count) multiply linked files entirely.",
	"    -U user     Report only usage by given user (specify name or id num).",
	NULL
};


static void do_help()
{
	char *s, *p;
	int i;

	for (i = 0 ; help_text[i] != NULL ; ++i) {
		for (s = help_text[i] ; *s != '\0' ; ++s) {
			if (*s != '%') {
				putchar(*s);
			} else {
				switch (*++s) {
					case 'B':
						printf("%d", REPORT_BLKSIZE);
						break;
					case 'C':
						for (p = Copyright ; *p != ' ' ; ++p)
							;
						fputs(p+1, stdout);
						break;
					case 'P':
						fputs(Progname, stdout);
						break;
					case 'V':
						fputs(VERSION, stdout);
						break;
					default:
						putchar('%');
						putchar(*s);
						break;
				}
			}
		}
		putchar('\n');
	}
	exit(0);
}


static void set_breakdown(str)
char *str;
{
	char *s;
	Num_break = 0;
	while ((s = strtok(str, " \t,")) != NULL) {
		str = NULL;
		if (Num_break >= MAX_BREAK)
			errmssg(ERR_ABORT, 0, "too many breakdown catagories");
		if ((Breakdown[Num_break++] = atoi(s)) <= 0 && strcmp(s, "0") != 0)
			errmssg(ERR_ABORT, 0, "bad breakdown value \"%s\"", s);
	}
	if (Num_break == 0)
		errmssg(ERR_ABORT, 0, "no breakdown catagories specified");
}


static int get_userid(id)
char *id;
{
	int n;
	struct passwd *pw;
	extern struct passwd *getpwnam();

	if ((n = atoi(id)) != 0 || strcmp(id, "0") == 0)
		return n;
	pw = getpwnam(id);
	endpwent();
	if (pw != NULL)
		return pw->pw_uid;
	errmssg(ERR_ABORT, 0, "bad user id \"%s\" specified", id);
	/*NOTREACHED*/
}


PTRTYPE *xmalloc(n)
unsigned n;
{
	PTRTYPE *s;
	if ((s = malloc(n)) == NULL)
		errmssg(ERR_ABORT, 0, "out of memory [malloc failed]");
	return s;
}


PTRTYPE *xrealloc(s, n)
PTRTYPE *s;
unsigned n;
{
	if ((s = realloc(s, n)) == NULL)
		errmssg(ERR_ABORT, 0, "out of memory [malloc failed]");
	return s;
}


/*
 * errmssg - Error reporting routine.  If "err" is nonzero then the
 * sys_errlist[] text will be included in the message.  If "severe" is
 * non-zero then the program will abort after displaying the message.
 * Non-fatal errors will be displayed only if "Do_print_errors" is TRUE.
 */
/*VARARGS*/
#ifdef USE_STDARG
void errmssg(int severe, int err, char *fmt, ...)
#else
void errmssg(va_alist)
va_dcl
#endif
{
	va_list args;
#ifndef USE_STDARG
	int severe, err;
	char *fmt;
#endif
	extern char *sys_errlist[];

#ifdef USE_STDARG
	va_start(args, fmt);
#else
	va_start(args);
	severe = va_arg(args, int);
	err = va_arg(args, int);
	fmt = va_arg(args, char *);
#endif

	if (severe || Do_print_errors) {
		fprintf(stderr, "%s: ", Progname);
		vfprintf(stderr, fmt, args);
		if (err > 0)
			fprintf(stderr, " [%s]", sys_errlist[err]);
		putc('\n', stderr);
	}

	va_end(args);

	if (severe)
		exit(1);
}


/*
 * Disk Usage Functions - Disk usage is accumulated (struct dusage) and
 * is used to track both disk usage and file counts chronologically.
 * The following routines manipulate this information.
 */


/*
 * Reset usage information to zero.
 */
void zero_usage(usage)
struct dusage *usage;
{
	register int *f, i;
	register long *b;

	b = usage->blocks;
	f = usage->files;
	i = Num_break;
	while (--i >= 0) {
		*b++ = 0L;
		*f++ = 0;
	}
}

/*
 * Load usage information from file size and age.
 */
void set_usage(usage, mtime, nblocks)
struct dusage *usage;
time_t mtime;
long nblocks;
{
	register int *f, i;
	register long *b;
	int ndays, *brk;

	/*
	 * There is a race condition here which luckily works out OK.  If a file
	 * is created after "du" is started, the "Curr_time-sbufp->st_mtime" value
	 * will be a small negative number, however dividing by "60*60*24" will
	 * truncate the result to zero, which is the result we want.
	 */
	ndays = (int)((Curr_time-mtime) / (60L*60L*24L/*seconds*/));

	b = usage->blocks;
	f = usage->files;
	brk = Breakdown;
	i = Num_break;
	while (--i >= 0) {
		if (*brk++ <= ndays) {
			*b++ = nblocks;
			*f++ = 1;
		} else {
			*b++ = 0L;
			*f++ = 0;
		}
	}
}

/*
 * Accumulate usage information.
 */
void add_usage(tot_usage, ent_usage)
register struct dusage *tot_usage, *ent_usage;
{
	register int i;

	i = Num_break;
	while (--i >= 0) {
		tot_usage->blocks[i] += ent_usage->blocks[i];
		tot_usage->files[i] += ent_usage->files[i];
	}
}

/*
 * Display usage information.
 */
void print_usage(name, usage)
char *name;
struct dusage *usage;
{
	int i;
	for (i = 0 ; i < Num_break ; ++i) {
		if (Do_file_counts)
			printf("%ld\t%d\t", usage->blocks[i], usage->files[i]);
		else
			printf("%ld\t", usage->blocks[i]);
	}
	printf("%s\n", name);
}

