[net.sources] cobwebs - mailbox size/age report

uggworek@sunybcs.UUCP (Don Gworek) (06/22/85)

Here is a program which reports unusually large, or unusually
old, mailboxes in /usr/spool/mail.  

When the user file system is under disc quota, the mail system
can often be used as "secondary" storage.  This program points
out users with unusually large amounts of unread mail.

The program will report for specific usernames, and sort either
alphabetically, by mailbox size, or mailbox age.  A quiet option
supresses the top banner.

# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".
#
# This archive contains:
# cobwebs.1 cobwebs.c

echo x - cobwebs.1
sed -e 's/^X//' > "cobwebs.1" << '//E*O*F cobwebs.1//'
X.TH COBWEBS 1 "21 June 1985"
X.SH NAME
Xcobwebs
X\- check for unusually large or old mailboxes
X.SH SYNOPSIS
X.B cobwebs
X[
X-agq
X]
X[
X-d days
X]
X[
X-s size
X]
X[
Xusername ...
X]
X.PP
X.SH DESCRIPTION
X.I Cobwebs
Xreports the size and age of mailboxes in /usr/spool/mail,
Xplus the last login of the mailbox owners.  The default
Xis to report mailboxes which are quite large -- larger than
Xthe default size, 20000 bytes.
X.PP
XWith the -d option, 
X.I cobwebs 
Xreports mailboxes that are unusually old --
Xthe default time is 30 days.
X.I Cobwebs
Xalso reports the mailbox status for usernames (command line
Xor piped).  And the program makes a general report of all
Xmailboxes, with the -g option.
X.SH OPTIONS
X.PP
X-a   Sort alphabetically.  Use this option with -d or -s.
X.PP
X-d   Sort and/or search by age in days.  The default is
X30 days, or the number argument is used.
X.PP
X-g   General report \-- status of all mailboxes.  By
Xdefault, this report is alphabetic.  -d or -s will allow
Xsort by age or size.
X.PP
X-q   Quiet option \-- no top banner.
X.PP
X-s   Sort and/or search by size in bytes.  Default is 
X20000 bytes, or the number argument is used.
X.SH FILES
X/usr/spool/mail /etc/passwd /usr/adm/lastlog
X.SH AUTHOR
XDon Gworek
//E*O*F cobwebs.1//

ls -l cobwebs.1

echo x - cobwebs.c
sed -e 's/^X//' > "cobwebs.c" << '//E*O*F cobwebs.c//'
X/*
X * cobwebs [-agq] [-d Days] [-s Size] [username ...]
X * 
X * -a alphabetic sort
X * -d sort and/or search by age     (default DEF_DAYS)
X * -g all mailboxes
X * -q quiet option -- no top banner
X * -s sort and/or search by size    (default DEF_SIZE)
X *
X *  Default: cobwebs -s
X *
X *  In case of multiple flags:
X *  	Sort priority   -- alpha > days > size
X *  	Search priority -- general > usernames > days > size
X *
X */
X
X#include <sys/param.h>
X#include <stdio.h>
X#include <sys/dir.h>
X#include <sys/stat.h>
X#include <lastlog.h>
X#include <pwd.h>
X
X#define DEF_SIZE 20000			/* default mbox size limit */
X#define DEF_DAYS 30			/* default mbox age limit */
X#define MAXSTRING 30
X#define MAXUSERNAMES 400
X#define MAXPACKS 1000
X#define TRUE 1
X#define FALSE 0
X
X#define NMAX 10
X
Xstruct packet
X{
X    char    name[NMAX];
X    off_t   size;
X    time_t  filetime;
X};
X
Xstruct packet  *calloc ();
Xstruct packet  *packs[MAXPACKS];
Xstruct packet **packp;
Xstruct stat stbuf;
X
Xchar   *malloc ();
Xchar    usernames[MAXUSERNAMES][NMAX];
Xint     num_unames = 0;			/* number of usernames		*/
Xint     now;
Xint     pack_count = 0;
Xint     mbox_size = DEF_SIZE;		/* default mbox constraint	*/
Xint     mbox_days = DEF_DAYS;		/* default mailbox age		*/
Xlong int mbox_clock = 0;		/* convert above to seconds	*/
Xlong int size_accum = 0;
Xlong int age_diff_accum = 0;
Xint	mbox_total = 0;
Xint     rep_names = FALSE;		/* name search flag		*/
Xint     rep_alpha = FALSE;		/* alphabetic sort flag		*/
Xint     rep_gen = FALSE;		/* all mailboxes flag		*/
Xint     rep_size = FALSE;		/* sort and/or search by size	*/
Xint     rep_days = FALSE;		/* sort and/or search by age	*/
Xint	rep_quiet = FALSE;		/* print the top banner		*/
X
Xscmp (p, q)
Xchar *p, *q;
X{
X    return (strcmp (p, q));
X}
X
Xmain (argc, argv)
Xint     argc;
Xchar  *argv[];
X{
X    int i;
X    (void) time (&now);
X    while ((argc-- > 0) && (*(*++argv) == '-'))
X	switch (*(*argv + 1))
X	{
X	case 's':
X	    if (*(*argv + 2) != '\0')
X		str_to_int (*argv, 2, &mbox_size);
X	    else
X	        if ((argc>1) && (**(argv+1) >= '0') && (**(argv+1) <= '9'))
X		{
X		    str_to_int (*++argv, 0, &mbox_size);
X		    argc--;
X		}
X	    rep_size = TRUE;
X	    break;
X	case 'd':
X	    if (*(*argv + 2) != '\0')
X		str_to_int (*argv, 2, &mbox_days);
X	    else
X	        if ((argc>1) && (**(argv+1) >= '0') && (**(argv+1) <= '9'))
X		{
X		    str_to_int (*++argv, 0, &mbox_days);
X		    argc--;
X		}
X	    mbox_clock = now - 86400 * mbox_days;
X	    rep_days = TRUE;
X	    break;
X	default :
X	    i = 1;
X	    do
X	        switch (*(*argv + i))
X		{
X		case 'a' : rep_alpha = TRUE; break;
X		case 'g' : rep_gen = TRUE; break;
X		case 'q' : rep_quiet = TRUE; break;
X		default  : usage ();
X		}
X	    while (*(*argv + (++i)) != '\0');
X	    break;
X    	}
X    if ((argc > 0) && !rep_gen)
X    {
X	while ((argc-- > 0) && (num_unames < MAXUSERNAMES))
X	    (void) strcpy (usernames[num_unames++], *argv++);
X	if (num_unames >= MAXUSERNAMES)
X	    fatal ("Error: MAXUSERNAMES %d reached\n", MAXUSERNAMES);
X	rep_names = TRUE;
X    }
X    if (!isatty (0) && !rep_gen)
X    {
X	while (getname (usernames[num_unames++], NMAX) > 0);
X	if (num_unames >= MAXUSERNAMES)
X	    fatal ("Error: MAXUSERNAMES %d reached\n", MAXUSERNAMES);
X	rep_names = TRUE;
X    }
X    if (rep_names)
X    {
X	qsort (usernames, num_unames, sizeof usernames[0], scmp);
X	if (!rep_size && !rep_days)
X	    rep_alpha = TRUE;
X    }
X    else 
X	if (rep_gen && !rep_days && !rep_size)
X	    rep_alpha = TRUE;				/* -g default */
X	else
X	    if (rep_days)
X		rep_size = FALSE;			/* no conflict */
X	    else 
X		rep_size = TRUE;			/* default */
X    check_mailboxes ();
X}
X
Xsize_cmp (p, q)
Xstruct packet **p, **q;
X{
X    if ((*p)->size == (*q)->size)
X	return (0);
X    if ((*p)->size > (*q)->size)
X	return (-1);
X    return (1);
X}
X
Xalpha_cmp (p, q)
Xstruct packet **p, **q;
X{
X    return (strcmp ((*p)->name, (*q)->name));
X}
X
Xfiletime_cmp (p, q)
Xstruct packet **p, **q;
X{
X    if ((*p)->filetime == (*q)->filetime)
X	return (0);
X    if ((*p)->filetime > (*q)->filetime)
X	return (1);
X    return (-1);
X}
X
Xcheck_mailboxes ()
X{
X    DIR * etc;
X    struct direct  *dp;
X    if (chdir ("/usr/spool/mail") < 0) {
X	perror ("/usr/spool/mail"); exit (1);
X    }
X    if ((etc = opendir (".")) == NULL) {
X	perror ("/usr/spool/mail"); exit (1);
X    }
X    packp = packs;
X    while (dp = readdir (etc))
X    {
X	if (dp->d_ino == 0)
X	    continue;
X	if (stat (dp->d_name, &stbuf) < 0)
X	    continue;
X	if ((stbuf.st_mode & S_IFMT) == S_IFDIR)
X	    continue;
X	if (pack_count >= MAXPACKS)
X	    fatal ("%d MAXPACKS reached -- report aborted\n", MAXPACKS);
X	if (rep_names)
X	{
X	    if (binsearch (dp->d_name))
X	    {
X	    take_note (dp->d_name);
X	    if (pack_count >= num_unames)
X	    	goto sortpacks;			/* all names found */
X	    }
X	    continue;
X	}
X	mbox_total++;
X	age_diff_accum += (stbuf.st_ctime - now);
X	size_accum += stbuf.st_size;
X	if ((rep_gen)
X		|| (rep_days && (stbuf.st_ctime < mbox_clock))
X		|| (rep_size && (stbuf.st_size > mbox_size)))
X	    take_note (dp->d_name);
X    }
Xsortpacks: 
X    if (rep_alpha)
X	qsort (packs, packp - packs, sizeof packs[0], alpha_cmp);
X    else
X	if (rep_days)
X	    qsort (packs, packp - packs, sizeof packs[0], filetime_cmp);
X	else
X	    qsort (packs, packp - packs, sizeof packs[0], size_cmp);
X    printout ();
X}
X
Xtake_note (name)
Xchar   *name;
X{
X    pack_count++;
X    *packp = calloc (1, sizeof (struct packet));
X    (void) strcpy ((*packp)->name, name);
X    (*packp)->size = stbuf.st_size;
X    (*packp)->filetime = stbuf.st_ctime;
X    packp++;
X}
X
Xchar   *
Xage_is (period)
Xlong int    period;
X{
X    int     days, hours;
X    char   *string, temp[MAXSTRING];
X    string = malloc (16);
X    *string = '\0';
X    days = period / 86400;
X    hours = (period % 86400) / 3600;
X    switch (days)
X    {
X	case 0: 
X	    (void) sprintf (string, "         ");
X	    break;
X	case 1: 
X	    (void) sprintf (string, "%3d Day  ", days);
X	    break;
X	default: 
X	    (void) sprintf (string, "%3d Days ", days);
X    }
X    switch (hours)
X    {
X	case 0: 
X	    (void) sprintf (temp, "%2d Mins ",
X				 (((period % 86400) % 3600) / 60));
X	    break;
X	case 1: 
X	    (void) sprintf (temp, "%2d Hour ", hours);
X	    break;
X	default: 
X	    (void) sprintf (temp, "%2d Hours", hours);
X	    break;
X    }
X    (void) strcat (string, temp);
X    return (string);
X}
X
Xprintout ()
X{
X    register struct packet **p;
X    int     f;
X    struct passwd  *pwd;
X    struct lastlog  ll;
X    int     i;
X    char   *s, host[10];
X    if ((f = open ("/usr/adm/lastlog", 0)) < 0) {
X	perror ("/usr/adm/lastlog"); exit (1);
X    }
X    if (!rep_quiet)
X    {
X    	if (pack_count == 0)
X    	{
X	    printf ("No Report.\n");
X	    return;
X    	}
X	for (i = 65; i-- > 0; putchar ('-'));
X	putchar ('\n');
X	(void) gethostname (host, 10);
X	s = (char *) ctime (&now);
X	printf ("%10s     %.12s     ", host, (s + 4));
X	if (pack_count == 1)
X	    printf ("%d Mailbox", pack_count);
X	else
X	    printf ("%d Mailboxes", pack_count);
X	if (rep_names && (num_unames != pack_count))
X	     printf (" Found For %d Names", num_unames);
X	else
X	    if (!rep_gen && !rep_names)
X	    {
X	    	if (rep_days)
X		    printf (" Older Than %d Days", mbox_days);
X	    	else
X		    if (rep_size)
X		    	printf (" Larger Than %d", mbox_size);
X	    }
X	if (!rep_names)
X		printf ("\n\nTotal: %d   Average Size: %d   Average Age:%-16s",
X		 mbox_total, (size_accum/mbox_total),
X		 age_is ((age_diff_accum + now) / mbox_total));
X	printf ("\n\n  NAME    MAILBOX SIZE");
X	printf ("      MAILBOX AGE           LAST LOGIN\n");
X	for (i = 65; i-- > 0; putchar ('-'));
X	putchar ('\n');
X    }
X    for (p = packs; p < packp; p++)
X    {
X	printf ("%-8s", (*p)->name);
X	printf ("     %6d     ", (*p)->size);
X	printf ("%-16s", age_is (now - (*p)->filetime));
X	if (((pwd = getpwnam ((*p)->name)) != NULL)
X	    && (lseek (f, (long) pwd->pw_uid * sizeof (struct lastlog), 0) >= 0)
X	   	&& (read (f, (char *) & ll, sizeof ll) == sizeof ll)
X	    	&& (ll.ll_time <= 0))
X	    printf ("        no login record\n");
X	else
X	{
X	    s = (char *) ctime (&ll.ll_time);
X	    printf ("        %.*s,%.*s\n", 24 - 18, (s + 4), 5, (s + 19));
X	}
X    }
X    (void) close (f);
X}
X
Xbinsearch (target)
Xchar   *target;
X{
X    int     hi, lo, mid, val;
X    lo = 0;
X    hi = num_unames - 1;
X    while (TRUE)
X	if (hi < lo)
X	    return (FALSE);
X	else
X	    if ((val = strcmp (target, usernames[(mid = (lo + hi) / 2)])) == 0)
X		return (TRUE);
X	    else
X		if (val > 0)
X		    lo = mid + 1;
X		else
X		    hi = mid - 1;
X}
X
Xstr_to_int (string, offset, num)
Xchar string[];
Xint    offset, *num;
X{
X    int     i, place, sum;
X    for (place = 1, sum = 0, i = strlen (string); i-- > offset; place *= 10)
X    {
X	if ((string [i] < '0') || (string [i] > '9'))
X	    usage ();
X	else
X	    sum = sum + (string [i] - '0') * place;
X    }
X    *num = sum;
X}
X
Xgetname (line, lengthlim)
Xchar    line[];
Xint     lengthlim;
X{
X    int     i;
X    char    ch;
X    for (i=0; i < lengthlim-1 && ((ch = getchar ()) != EOF)
X		&& (ch != '\n') && (ch != ' ') && (ch != '\t');)
X	line[i++] = ch;
X    line[i] = '\0';
X    return (i);
X}
X
Xusage ()
X{
X    fprintf (stderr, "Usage: cobwebs [-agq] [-d days] ");
X    fprintf (stderr, "[-s size] [username ...]\n");
X    exit (1);
X}
X
Xfatal (format, argument)
Xchar   *format, *argument;
X{
X    fprintf (stderr, format, argument);
X    exit (1);
X}
//E*O*F cobwebs.c//

ls -l cobwebs.c

exit 0