/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 *
 */

/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"$Header: diskusg.c 1.11 90/01/26 $@(#)acct:diskusg.c	1.10"
#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/ufsinode.h>
#include <sys/stat.h>
#include <sys/ufsfilsys.h>
#include <sys/sysmacros.h>
#include <pwd.h>
#include <fcntl.h>

#ifndef Fs2BLK
#define Fs2BLK	0
#endif

#ifndef FsINOS
#define FsINOS(dev, x)	((x&~07)+1)
#endif

#define BLOCK		512	/* Block size for reporting */

#if pdp11
#define		NINODE		(INOPB * 2 * 10)
#endif
/* #define		MAXUSERS	1000 Now dynamically computed */
#define		MAXIGN		100
#define		UNUSED		-1
#define		FAIL		-1
#define		MAXNAME		8
#define		SUCCEED		0
#define		TRUE		1
#define		FALSE		0
#define		FsBSIZE(dev)	dev

int MAXUSERS;

char	*valloc();
struct  passwd *getpwent();

int	VERBOSE = 0;
FILE	*ufd = 0;
int	index;

struct acct  {
	int	uid;
	unsigned long	usage;
	char	name [MAXNAME+1];
} *userlist;

char	*ignlist[MAXIGN];
int	igncnt = {0};

char	*cmd;


unsigned hash();
main(argc, argv)
int argc;
char **argv;
{
	extern	int	optind;
	extern	char	*optarg;
	register c;
	register FILE	*fd;
	register	rfd;
	struct	stat	sb;
	int	sflg = {FALSE};
	char	*pfile = {"/etc/passwd"};
	int	errfl = {FALSE};

	cmd = argv[0];
	while((c = getopt(argc, argv, "vu:p:si:")) != EOF) switch(c) {
	case 's':
		sflg = TRUE;
		break;
	case 'v':
		VERBOSE = 1;
		break;
	case 'i':
		ignore(optarg);
		break;
	case 'u':
		ufd = fopen(optarg, "a");
		break;
	case 'p':
		pfile = optarg;
		break;
	case '?':
		errfl++;
		break;
	}
	if(errfl) {
		fprintf(stderr, "Usage: %s [-sv] [-p pw_file] [-u file] [-i ignlist] [file ...]\n", cmd);
		exit(10);
	}

	MAXUSERS = compute_users(pfile);	/* size the password file */

	hashinit();
	if(sflg == TRUE) {
		if(optind == argc){
			adduser(stdin);
		} else {
			for( ; optind < argc; optind++) {
				if( (fd = fopen(argv[optind], "r")) == NULL) {
					fprintf(stderr, "%s: Cannot open %s\n", cmd, argv[optind]);
					continue;
				}
				adduser(fd);
				fclose(fd);
			}
		}
	}
	else {
		setup(pfile);
		for( ; optind < argc; optind++) {
			if( (rfd = open(argv[optind], O_RDONLY)) < 0) {
				fprintf(stderr, "%s: Cannot open %s\n", cmd, argv[optind]);
				continue;
			}
			if(fstat(rfd, &sb) >= 0){
				if ( (sb.st_mode & S_IFMT) == S_IFCHR ||
				     (sb.st_mode & S_IFMT) == S_IFBLK ) {
					ilist(argv[optind], rfd);
				} else {
					fprintf(stderr, "%s: %s is not a special file -- ignored\n", cmd, argv[optind]);
				}
			} else {
				fprintf(stderr, "%s: Cannot stat %s\n", cmd, argv[optind]);
			}
			close(rfd);
		}
	}
	output();
	exit(0);
}

adduser(fd)
register FILE	*fd;
{
	int	usrid;
	unsigned long	blcks;
	char	login[MAXNAME+10];

	while(fscanf(fd, "%d %s %lu\n", &usrid, login, &blcks) == 3) {
		if( (index = hash(usrid)) == FAIL) return(FAIL);
		if(userlist[index].uid == UNUSED) {
			userlist[index].uid = usrid;
			strncpy(userlist[index].name, login, MAXNAME);
			userlist[index].name[MAXNAME] = '\0';
		}
		userlist[index].usage += blcks;
	}
}

ilist(file, fd)
char	*file;
register fd;
{
	register cgx;
	static struct cg *cgp; /* pointer to cylinder group (valloc'd) */
	static struct	fs	*sblock;

	if (fd < 0 ) {
		return (FAIL);
	}

	sync();

	if (sblock == NULL) {
		if ((cgp = (struct cg *)valloc(MAXBSIZE)) == NULL) {
			fprintf(stderr, "%s: out of memory\n", cmd);
			exit(1);
		}
		if ((sblock = (struct fs *)valloc(SBSIZE)) == NULL) {
			fprintf(stderr, "%s: out of memory\n", cmd);
			exit(1);
		}
	}

	/* Read in super-block of filesystem */
	bread(fd, SBLOCK, sblock, SBSIZE);
	/* Check file system magic number */
	if (sblock->fs_magic != FS_MAGIC ) {
		fprintf(stderr, "%s: %s is not a filesystem\n", cmd, file);
		exit(1);
	}

	/* Check for filesystem names to ignore */
	if(!todo(sblock->fs_fname))
		return(0);

	/*
	 * index through the cylinder group numbers.
	 */
	for (cgx=0; cgx < sblock->fs_ncg; cgx++) {
		bread(fd, fsbtodb(sblock, cgtod(sblock, cgx)), cgp, MAXBSIZE);
		if (!cg_chkmagic(cgp)) {
			fprintf(stderr, "bad magic in cylinder group %d\n",
				cgx);
			exit(1);
		}
		cgilist(file, fd, sblock, cgp);
	}
	return (0);
}

/*
 * process the inodes within a cylinder group
 *	cgp is a pointer to the cylinder group structure being processed.
 *	curip points to the running inode number counter.
 */
cgilist(file, fd, fs, cgp)
char *file;
int fd;
struct fs *fs;
struct cg *cgp;
{
	struct	dinode	*dip;
	daddr_t	iblkno = -1, niblkno;
	ino_t	curi;
	int i;
	char	*imp;
	static char *inobuf;

	if (inobuf == (char *)NULL) {
		if ((inobuf = valloc(MAXBSIZE)) == NULL) {
			fprintf(stderr, "out of memory\n");
			exit(1);
		}
	}

	curi = cgp->cg_cgx * fs->fs_ipg;
	for (imp = cg_inosused(cgp); imp < &cg_inosused(cgp)[fs->fs_ipg/NBBY];
		imp++) {
		if (*imp == 0) {
			curi += NBBY;
			continue;
		}
		for (i = 0; i < NBBY; i++) {
			if ((curi < 2) || !((*imp)&(1 << i))) {
				curi++;
				continue;
			}
			if (iblkno != (niblkno = itod(fs, curi))) {
				bread(fd, fsbtodb(fs, niblkno), inobuf,
					MAXBSIZE);
				iblkno = niblkno;
			}
			dip = (struct dinode *)inobuf + (curi)%INOPB(fs);
			if (dip->di_mode == 0) {
				fprintf(stderr, "allocated inode is free %d\n", 
					curi);
				exit(1);
			}
		
			if(count(dip, fs) == FAIL) {
				if(VERBOSE)
					fprintf(stderr,
		"BAD UID: file system = %s, inode = %u, uid = %u\n",
				    	file, curi, dip->di_uid);
				if(ufd)
					fprintf(ufd,
		"%s %u %u\n", file, curi, dip->di_uid);
			}
			curi++;
		}
	}
}

ignore(str)
register char	*str;
{
	char	*skip();

	for( ; *str && igncnt < MAXIGN; str = skip(str), igncnt++)
		ignlist[igncnt] = str;
	if(igncnt == MAXIGN) {
		fprintf(stderr, "%s: ignore list overflow. Recompile with larger MAXIGN\n", cmd);
	}
}
bread(fd, bno, buf, cnt)
register fd;
register unsigned bno;
register char *buf;
{
	lseek(fd, (long)dbtob(bno), 0);
	if (read(fd, buf, cnt) != cnt)
	{
		fprintf(stderr, "read error %u\n", bno);
		exit(1);
	}
}

count(dip, fs)
register struct dinode *dip;
register struct fs *fs;
{
	unsigned long	nblocks();

	if (dip->di_nlink == 0 || dip->di_mode == 0 )
		return(SUCCEED);
	if( (index = hash(dip->di_uid)) == FAIL || userlist[index].uid == UNUSED )
		return (FAIL);
	userlist[index].usage +=
		nblocks(dip->di_size, fs->fs_bsize, fs->fs_fsize);
	return (SUCCEED);
}


output()
{
	for (index=0; index < MAXUSERS ; index++)
		if ( userlist[index].uid != UNUSED && userlist[index].usage != 0 )
			printf("%u	%s	%lu\n",
			    userlist[index].uid,
			    userlist[index].name,
			    userlist[index].usage);
}

/*
 * The following nblocks() routine is identical to the one
 * in libseq.a, but this one turns on the USE_FRAGS define.
 * This allows the accounting software to be more correct
 * since it is only supported on 4.2 type filesystems.
 */
#define USE_FRAGS
#define SNGLIND	(bsize/sizeof(daddr_t))
#define DBLIND	(SNGLIND*SNGLIND)

unsigned long
nblocks(size, bsize, fsize)
register long size, bsize, fsize;
{
	register unsigned long blks, remainder, frags = 0;
	register unsigned long tot;

#ifdef USE_FRAGS
	/* partial blocks allocated in frag size chunks */
	if (size < (NDADDR * bsize)) {
		blks = size / bsize;
		if (remainder = size % bsize) {
			frags = (remainder + fsize - 1) / fsize;
		}
		blks *= bsize / DEV_BSIZE;
		blks += frags * (fsize / DEV_BSIZE);
		return(blks);
	}
#else
	/* partial blocks allocated in block size chunks */
	if (size < (NDADDR * bsize)) {
		blks = size / bsize;
		if (size % bsize) {
			blks++;
		}
		return(blks * bsize / DEV_BSIZE);
	}
#endif /* USE_FRAGS */

	blks = (size + bsize - 1) / bsize;
	tot = blks;
	if (blks > NDADDR) {
		tot += (blks-NDADDR-1)/SNGLIND + 1;
		if (blks > NDADDR+SNGLIND)
			tot += (blks-NDADDR-SNGLIND-1)/DBLIND + 1;
		if (blks > NDADDR+SNGLIND+DBLIND)
			tot ++;
	}
	blks = tot * bsize / DEV_BSIZE;
	return(blks);
}

unsigned
hash(j)
register unsigned j;
{
	register unsigned start;
	register unsigned circle;
	circle = start = j % MAXUSERS;
	do
	{
		if ( userlist[circle].uid == j || userlist[circle].uid == UNUSED )
			return (circle);
		circle = (circle + 1) % MAXUSERS;
	} while ( circle != start);
	return (FAIL);
}

hashinit() {
	for(index=0; index < MAXUSERS ; index++)
	{
		userlist[index].uid = UNUSED;
		userlist[index].usage = 0;
		userlist[index].name[0] = '\0';
	}
}

/*
 * compute_users()
 *
 * Find number of lines in password file.  This tells us how many
 * uid's we might find, and thus how large the table for them
 * must be.
 */

compute_users(pfile)
	char *pfile;
{
	FILE *fp;
	int count = 0;
	char buf[BUFSIZ];

	if ((fp = fopen(pfile, "r")) == NULL) {
		fprintf(stderr, "%s: Cannot open %s\n", cmd, pfile);
		exit(6);
	}

	while (fgets(buf, BUFSIZ, fp) != NULL)
		count++;
	fclose(fp);

	/* round up to next increment of 200 */

	count += 200;
	count /= 100;
	count *= 100;

	userlist = (struct acct *) malloc(sizeof(struct acct) * count);
	if (userlist == (struct acct *) NULL) {
		fprintf(stderr, "%s: Cannot malloc space for user list table.\n",
		    cmd);
		exit(7);
	}
	return(count);
}

setup(pfile)
char	*pfile;
{
	register struct passwd	*pw;

	if( !setpwent(pfile)) {
		fprintf(stderr, "%s: Cannot open %s\n", cmd, pfile);
		exit(5);
	}
	while ( (pw=getpwent()) != NULL )
	{
		if ( (index=hash(pw->pw_uid)) == FAIL )
		{
			fprintf(stderr,"diskacct: INCREASE SIZE OF MAXUSERS\n");
			return (FAIL);
		}
		if ( userlist[index].uid == UNUSED )
		{
			userlist[index].uid = pw->pw_uid;
			strncpy( userlist[index].name, pw->pw_name, MAXNAME);
			userlist[index].name[MAXNAME] = '\0';
		}
	}
}

todo(fname)
register char	*fname;
{
	register	i;

	for(i = 0; i < igncnt; i++) {
		if(strncmp(fname, ignlist[i], 6) == 0) return(FALSE);
	}
	return(TRUE);
}

char	*
skip(str)
register char	*str;
{
	while(*str) {
		if(*str == ' ' ||
		    *str == ',') {
			*str = '\0';
			str++;
			break;
		}
		str++;
	}
	return(str);
}

extern long atol();
extern char *fgets();

static FILE *pwf = NULL;
static char line[BUFSIZ+1];
static struct passwd passwd;

setpwent(pfile)
register char *pfile;
{
	if(pwf == NULL)
		pwf = fopen(pfile, "r");
	else
		rewind(pwf);
	return(pwf != NULL);
}

void
endpwent()
{
	if(pwf != NULL) {
		(void) fclose(pwf);
		pwf = NULL;
	}
}

static char *
pwskip(p)
register char *p;
{
	while(*p && *p != ':' && *p != '\n')
		++p;
	if(*p == '\n')
		*p = '\0';
	else if(*p)
		*p++ = '\0';
	return(p);
}

struct passwd *
getpwent()
{
	register char *p;
	long	x;

	if(pwf == NULL) {
		return(0);
	}
	p = fgets(line, BUFSIZ, pwf);
	if(p == NULL)
		return(0);
	passwd.pw_name = p;
	p = pwskip(p);
	passwd.pw_passwd = p;
	p = pwskip(p);
	x = atol(p);	
	passwd.pw_uid = x;
	p = pwskip(p);
	x = atol(p);
	passwd.pw_gid = x;
	p = pwskip(p);
	passwd.pw_gecos = p;
	p = pwskip(p);
	passwd.pw_dir = p;
	p = pwskip(p);
	passwd.pw_shell = p;
	(void) pwskip(p);

	p = passwd.pw_passwd;
	while(*p && *p != ',')
		p++;
	if(*p)
		*p++ = '\0';
	passwd.pw_age = p;
	return(&passwd);
}
