/*
 * change_quotas
 *
 * allows group leaders to change quotas of certain people
 *
 * John Buck 6/83
 */
typedef	unsigned int	uint;
#include	"stdio.h"
#include	"quotas.h"

#define	DOVER		01
#define	FOVER		02
#define	CHANGE		04
#define	DISABLE		010
#define	ENABLE		020
#define	MAXUSERS	64
#define	BLOCKMAX	20000L
#define	FILEMAX		5000

#define	QUOLIST		"/etc/quotas_group"
#define	LOCKFILE	"/etc/quota.lock"
#define	LOCKLINK	"/etc/quota.link"
#define	LOGFILE		"/usr/adm/.changelog"

short	qfd;
short	change;
short	nusers;
short	lflag;
short	ftot;
short	uftot;
long	btot;
long	ubtot;

FILE	*fpo;
char	timef[32];
char	*myname;
short	funct;
char	ubuf[512];

int	intr();
long	atol();
short	*localtime();

struct	user	{
	char	u_lname[10];
	uint	u_uid;
	uint	u_gid;
	short	u_fquo;
	short	u_fused;
	long	u_dquo;
	long	u_dused;
	uint	u_mused;
	uint	u_mquo;
	uint	u_inc;
	short	u_flag;
	short	u_ent;
} ulist[MAXUSERS];



main(argc, argv)
char **argv;
{
	register char *s;
	register short i;

	myname = argv[0];
	lock();
	if((qfd = open(QUOFILE, 2)) == -1)
		error("Can not open quota file");
	if(argc != 1)
		lflag = 1;
	getusr();
	if(lflag == 1){
		close(qfd);
		list();
		unlock();
		exit(0);
	}
	if(!isatty(0))
		error("Must be run from a terminal");
	avail();
	while(1){
		fprintf(stdout, "\nCommand (List, Blocks, Files");
		fprintf(stdout, ", Money, Disable, Enable, Quit)? ");
		fflush(stdout);
		getl();
		if((i = ubuf[0] | 040) == 'q')
			break;
		if(i == 'l'){
			fputc('\n', stdout);
			list();
			fputc('\n', stdout);
			continue;
		}
		if(strchr("bfdme", i) == NULL){
			fprintf(stderr, "Invalid command -- retype\n");
			continue;
		}
		s = ubuf;
		while(*s && *s != ' ')s++;
		funct = i;
		if(*s++ == 0){
			fprintf(stdout, "\nEnter login name: ");
			fflush(stdout);
			getl();
			s = ubuf;
		}
		lookup(s);
	}
	if(change){
		for(i = 1; i < 16; i++)
			signal(i, 1);
		writeout();
	}
	unlock();
	exit(0);
}

getusr()
{
	register char *s, *t;
	FILE *fp;
	register char *ln;
	char *logname(), *strchr();
	short i;

	ln = logname();
	if((fp = fopen(QUOLIST, "r")) == NULL)
		error("Can not open list of valid users");
	while(fgets(ubuf, 510, fp) != NULL){
		if((s = strchr(ubuf, ':')) == NULL)
			error("User list is mal-formed");
		*s++ = NULL;
		if(strcmp(ubuf, ln))
			continue;
		fclose(fp);
		btot = atol(s);
		ubtot = 0L;
		uftot = 0;
		if(btot < 0L || btot > BLOCKMAX)
			error("Bad block total field");
		if((s = strchr(s, ':')) == NULL)
			error("Block/file fields are mal-formed");
		ftot = atoi(++s);
		if(ftot < 0 || ftot > FILEMAX)
			error("Bad file total field");
		if((s = strchr(s, ':')) == NULL)
			error("Name fields are mal-formed");
		nusers = 0;
		s++;
		t = s;
		while(*s && *s != '\n'){
			if(*s == ','){
				*s++ = 0;
				if(nusers >= MAXUSERS)
					error("Too many users under group");
				adduser(t);
				t = s;
				continue;
			}
			s++;
		}
		*s = 0;
		adduser(t);
		return;
	}
	error("\"%s\" is not a valid group leader", ln);
}

error(a1, a2, a3, a4, a5)
char *a1, *a2, *a3, *a4, *a5;
{
	fprintf(stderr, "%s: ", myname);
	fprintf(stderr, a1, a2, a3, a4, a5);
	fputc('\n', stderr);
	unlock();
	fflush(stderr);
	exit(1);
}

adduser(u)
register char *u;
{
	register struct user *pu;
	register char *s;

	while(*u && (*u == ' ' || *u == '\t'))
		u++;
	for(s = u; *s; s++);
	for(s--; *s == ' ' || *s == '\t'; s--);
	*++s = 0;
	pu = &ulist[nusers];
	if((pu->u_ent = getqent(qfd, &q, u)) == 0){
		fprintf(stderr, "%s: Can't find %s in quota file (warning)\n",
			myname, u);
		return;
	}
	pu->u_dquo = q.q_dquo;
	pu->u_dused = q.q_dused;
	pu->u_fquo = q.q_fquo;
	pu->u_fused = q.q_fused;
	strcpy(pu->u_lname, u);
	pu->u_uid = q.q_uid;
	pu->u_gid = q.q_gid;
	if((q.q_flg & 037) == 9)
		pu->u_flag = DISABLE;
	pu->u_mused = q.q_mused/10;
	pu->u_mquo = q.q_mquo/10;
	pu->u_inc = q.q_inc;
	if(q.q_dquo < q.q_dused)
		pu->u_flag |= DOVER;
	if(q.q_fquo < q.q_fused)
		pu->u_flag |= FOVER;
	ubtot += (long)(q.q_dquo);
	uftot += q.q_fquo;
	nusers++;
}

list()
{
	register short i;
	register struct user *pu;

	fprintf(stdout, "Username   Block Quota   File Quota");
	fprintf(stdout, "   Money Quota(Inc)   ");
	fprintf(stdout, " Uid   Gid\n");
	for(i = 0; i < nusers; i++){
		pu = &ulist[i];
		fprintf(stdout, "%-8.8s%c  %5D/%5D     %3u/%3u",
			pu->u_lname, pu->u_flag & DISABLE ? '*' : ' ',
			pu->u_dused, pu->u_dquo, pu->u_fused, pu->u_fquo);
		fprintf(stdout, "    $%4u/%5u(%3d)  ", pu->u_mused, 
			pu->u_mquo, pu->u_inc);
		fprintf(stdout, "%5d %5d  ", pu->u_uid, pu->u_gid);
		fputc(pu->u_flag & DOVER ? 'D' : ' ', stdout);
		fputc(pu->u_flag & FOVER ? 'F' : ' ', stdout);
		fputc('\n', stdout);
	}
	avail();
}

lock()
{
	short fd;
	short i;

	for(i = 1; i < 16; i++)
		if(signal(i, 1) != 1)
			signal(i, intr);
	if((fd = creat(LOCKFILE, 0600)) == -1)
		error("Can't creat lockfile");
	close(fd);
	if(link(LOCKFILE, LOCKLINK) == -1)
		error("Someone else is changing quotas now -- try again.");
	chown(LOCKFILE, 3, 3);
}

unlock()
{
	unlink(LOCKFILE);
	unlink(LOCKLINK);
}

intr()
{
	register short i;

	for(i = 1; i < 16; i++)
		signal(i, 1);
	unlock();
	close(qfd);
	exit(2);
}

lookup(u)
register char *u;
{
	register short i;
	register struct user *pu;
	short newf, needf, freef;
	long newq, needb, freeb;
	short pflg;
	char *ln;

	pflg = 0;
	ln = u;
	while(*u && *u != ' ' && *u != '\t')
		u++;
	if(*u){
		*u++ = 0;
		pflg = 1;
		newq = atol(u);
	}
	for(i = 0; i < nusers; i++)
		if(strcmp(ulist[i].u_lname, ln) == 0)break;
	if(i >= nusers){
		fprintf(stderr, "\"%s\" is not part of your quota group\n", ln);
		return;
	}

	pu = &ulist[i];
	switch(funct){
case 'b':
blockq:
	if(pflg){
		pflg = 0;
		goto gotq;
	}
	fprintf(stdout, "New block quota for \"%s\" <%D/%D>? ",
		 pu->u_lname, pu->u_dused, pu->u_dquo);
	fflush(stdout);
	if(fgets(ubuf, 256, stdin) == NULL)
		return;
	newq = atol(ubuf);
	if(ubuf[0] == '\n')
		return;
gotq:
	if(newq < 1L){
		fprintf(stderr, "Illegal number: %D\n", newq);
		goto blockq;
	}
	if(newq > pu->u_dquo){
		freeb = btot - ubtot;
		needb = newq - pu->u_dquo;
		if(needb > freeb){
			fprintf(stderr, "That would require %D block%s ", needb,
				needb == 1 ? "," : "s,");
			if(freeb == 0L)
				fprintf(stderr, "you don't have any to play with.\n");
			else	if(freeb < 0L)
				fprintf(stderr, "you are %D in the hole.\n", freeb);
			else	fprintf(stderr, "you only have %D to play with.\n", freeb);
			return;
		}
		if((pu->u_flag & DOVER) == 0 && someover(pu, DOVER))
			return;
	} else {
		if(pu->u_dused > newq){
			newq = pu->u_dused - newq;
			fprintf(stderr, "That would put \"%s\" over by %D block%s\n",
				pu->u_lname, newq, newq == 1L ? "" : "s");
			return;
		}
	}
	ubtot += (newq - pu->u_dquo);
	pu->u_dquo = newq;
	if(pu->u_flag & DOVER)
		if(pu->u_dquo >= pu->u_dused)pu->u_flag &= ~DOVER;
	else	fprintf(stderr, "\"%s\" is still over quota.\n", pu->u_lname);
	change = 1;
	pu->u_flag |= CHANGE;
	break;
case 'f':
fileq:
	if(pflg){
		pflg = 0;
		goto gotf;
	}
	fprintf(stdout, "New file quota for \"%s\" <%d/%d>? ",
		pu->u_lname, pu->u_fused, pu->u_fquo);
	fflush(stdout);
	if(fgets(ubuf, 256, stdin) == NULL)
		return;
	if(ubuf[0] == '\n')
		return;
	newq = atol(ubuf);
gotf:
	if(newq < 1L || newq > FILEMAX){
		fprintf(stderr, "Illegal number: %D\n", newq);
		goto fileq;
	}
	newf = newq;
	if(newf > pu->u_fquo){
		freef = ftot - uftot;
		needf = newf - pu->u_fquo;
		if(needf > freef){
			fprintf(stderr, "That would require %d file%s ", needf,
				needf == 1 ? "," : "s,");
			if(freef == 0)
				fprintf(stderr, "you don't have any to play with.\n");
			else if(freef < 0)
				fprintf(stderr, "you are %d in the hole.\n",
						freef);
			else fprintf(stderr, "you only have %d to play with.\n", freef);
			return;
		}
		if((pu->u_flag & FOVER) == 0 && someover(pu, FOVER))
			return;
	} else {
		if(pu->u_fused > newf){
			newf = pu->u_fused - newf;
			fprintf(stderr, "That would put \"%s\" over by %d file%s\n",
				pu->u_lname, newf, newf == 1 ? "" : "s");
			return;
		}
	}
	uftot += (newf - pu->u_fquo);
	pu->u_fquo = newf;
	if(pu->u_flag & FOVER)
		if(pu->u_fquo >= pu->u_fused)pu->u_flag &= ~FOVER;
	else	fprintf(stderr, "\"%s\" is still over quota.\n", pu->u_lname);
	change = 1;
	pu->u_flag |= CHANGE;
	break;

case 'd':
	pu->u_flag |= (DISABLE|CHANGE);
	change = 1;
	break;

case 'e':
	pu->u_flag = (pu->u_flag & ~DISABLE)|(CHANGE|ENABLE);
	change = 1;
	break;

case 'm':
	fprintf(stdout, "Enter new money_quota, money_inc, money_used ");
	fprintf(stdout, "(%u,%u,%u): ", pu->u_mquo, pu->u_inc, pu->u_mused);
	fflush(stdout);
	getl();
	u = ubuf;
	if(*u == 0 || *u == ',')goto next;
	newq = atol(u);
	if(newq < 0 || newq > 3000L){
		fprintf(stderr, "Bad money quota specified: %D\n", newq);
		return;
	}
	pu->u_mquo = newq;
	change = 1;
	pu->u_flag |= CHANGE;

next:
	while(*u && *u != ',')u++;
	if(*u++ == 0)
		return;
	if(*u == ' ')
		u++;
	if(*u == 0 || *u == ',')
		goto next1;
	newq = atol(u);
	if(newq < 0 || newq > 20){
		fprintf(stderr, "Bad increment for quotas: %D\n", newq);
		return;
	}
	pu->u_inc = newq;
	pu->u_flag |= CHANGE;
	change = 1;

next1:
	while(*u && *u != ',')
		u++;
	if(*u++ == 0)
		return;
	if(*u == ' ')u++;
	if(*u == 0)
		return;
	newq = atol(u);
	if(newq < 0 || newq > pu->u_mquo){
		fprintf(stderr, "Bad \"money used\" value: %D\n", newq);
		return;
	}
	pu->u_mused = newq;
	pu->u_flag |= CHANGE;
	change = 1;
	return;
	break;
	}
}

someover(pu, flag)
register struct user *pu;
register short flag;
{
	register short i;
	char err;

	err = 0;
	for(i = 0; i < nusers; i++){
		if(&ulist[i] != pu && (ulist[i].u_flag & flag)){
			err = 1;
			fprintf(stdout, "%s, ", ulist[i].u_lname);
		}
	}
	if(err)
		fprintf(stdout, "%s over quota -- you must fix %s first!\n",
			err == 1 ? "is" : "are", err == 1 ? "it" : "them");
	return(err);
}


writeout()
{
	register short *pt;
	register short i;
	long tv[2];

	time(tv);
	pt = localtime(tv);
	sprintf(timef, " %02d/%02d %02d:%02d ", pt[3], pt[4]+1, pt[2],
		pt[1]);
	fpo = fopen(LOGFILE, "a");
	for(i = 0; i < nusers; i++)
		update(&ulist[i]);
	if(fpo)fclose(fpo);
}

update(pu)
register struct user *pu;
{
	short r;

	if((pu->u_flag & CHANGE) == 0)
		return;

	lseek(qfd, (long)(pu->u_ent)*QSZ, 0);
	if((r = read(qfd, &q, QSZ)) != QSZ)
		error("Bad %s read: %d", QUOFILE, r);
	lseek(qfd, -(long)(QSZ), 1);
	if(strncmp(pu->u_lname, q.q_ln, 8))
		error("Name mismatch: %s, %s\n", pu->u_lname, q.q_ln);
	fprintf(fpo, "%s:%s \"%.8s\" was %u/%u now ", logname(),
		timef, q.q_ln,  q.q_dquo, q.q_fquo);
	fprintf(fpo, "%D/%u", pu->u_dquo, pu->u_fquo);
	q.q_dquo = (uint)(pu->u_dquo);
	q.q_fquo = (uint)(pu->u_fquo);
	q.q_mused = pu->u_mused*10;
	q.q_mquo = pu->u_mquo*10;
	q.q_inc = pu->u_inc;
	if((pu->u_flag & ENABLE) && (q.q_flg & 037) == 9){
		fprintf(fpo, " Enabled");
		q.q_flg = (q.q_flg & ~037);
	}
	if(pu->u_flag & DISABLE){
		q.q_flg = (q.q_flg & ~037) + 9;
		fprintf(fpo, " Disabled");
	}
	fprintf(fpo, "\n");
	fflush(fpo);
	if((r = write(qfd, &q, QSZ)) != QSZ)
		error("Bad %s write: %d", QUOFILE, r);
}

avail()
{
	fprintf(stdout, "\nTotal available blocks = %D", btot - ubtot);
	fprintf(stdout, "  Used/Allocation: %D/%D\n", ubtot, btot);
	fprintf(stdout, "Total available files = %u  ", ftot - uftot);
	fprintf(stdout, "Used/Allocation: %u/%u\n", uftot, ftot);
}

getl()
{
	register char *s, *t;
	register short i;

	s = ubuf;
	*s = 0;
	if(fgets(s, 256, stdin) == NULL)
		return;
	t = s;
	i = 0;
	while(*s && *s != '\n'){
		if(*s == ' ' || *s == '\t')
			i = 1;
		else	{
			if(i){
				i = 0;
				*t++ = ' ';
			}
			*t++ = *s;
		}
		s++;
	}
	*t = 0;
}

