quota.c revision 13365
1/*
2 * Copyright (c) 1980, 1990, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Robert Elz at The University of Melbourne.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 *    must display the following acknowledgement:
18 *	This product includes software developed by the University of
19 *	California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 *    may be used to endorse or promote products derived from this software
22 *    without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#ifndef lint
38static char copyright[] =
39"@(#) Copyright (c) 1980, 1990, 1993\n\
40	The Regents of the University of California.  All rights reserved.\n";
41#endif /* not lint */
42
43#ifndef lint
44static char sccsid[] = "from: @(#)quota.c	8.1 (Berkeley) 6/6/93";
45#endif /* not lint */
46
47/*
48 * Disk quota reporting program.
49 */
50#include <sys/param.h>
51#include <sys/types.h>
52#include <sys/file.h>
53#include <sys/stat.h>
54#include <sys/mount.h>
55#include <sys/socket.h>
56#include <ufs/ufs/quota.h>
57#include <stdio.h>
58#include <stdlib.h>
59#include <fstab.h>
60#include <ctype.h>
61#include <string.h>
62#include <pwd.h>
63#include <grp.h>
64#include <errno.h>
65
66#include <netdb.h>
67#include <rpc/rpc.h>
68#include <rpc/pmap_prot.h>
69#include <rpcsvc/rquota.h>
70
71char *qfname = QUOTAFILENAME;
72char *qfextension[] = INITQFNAMES;
73
74struct quotause {
75	struct	quotause *next;
76	long	flags;
77	struct	dqblk dqblk;
78	char	fsname[MAXPATHLEN + 1];
79};
80#define	FOUND	0x01
81
82char *timeprt	__P((time_t seconds));
83struct quotause *getprivs __P((long id, int quotatype));
84
85int	qflag;
86int	vflag;
87
88main(argc, argv)
89	char *argv[];
90{
91	int ngroups;
92	gid_t mygid, gidset[NGROUPS];
93	int i, gflag = 0, uflag = 0;
94	char ch;
95	extern char *optarg;
96	extern int optind, errno;
97
98	while ((ch = getopt(argc, argv, "ugvq")) != EOF) {
99		switch(ch) {
100		case 'g':
101			gflag++;
102			break;
103		case 'u':
104			uflag++;
105			break;
106		case 'v':
107			vflag++;
108			break;
109		case 'q':
110			qflag++;
111			break;
112		default:
113			usage();
114		}
115	}
116	argc -= optind;
117	argv += optind;
118	if (!uflag && !gflag)
119		uflag++;
120	if (argc == 0) {
121		if (uflag)
122			showuid(getuid());
123		if (gflag) {
124			mygid = getgid();
125			ngroups = getgroups(NGROUPS, gidset);
126			if (ngroups < 0) {
127				perror("quota: getgroups");
128				exit(1);
129			}
130			showgid(mygid);
131			for (i = 0; i < ngroups; i++)
132				if (gidset[i] != mygid)
133					showgid(gidset[i]);
134		}
135		exit(0);
136	}
137	if (uflag && gflag)
138		usage();
139	if (uflag) {
140		for (; argc > 0; argc--, argv++) {
141			if (alldigits(*argv))
142				showuid(atoi(*argv));
143			else
144				showusrname(*argv);
145		}
146		exit(0);
147	}
148	if (gflag) {
149		for (; argc > 0; argc--, argv++) {
150			if (alldigits(*argv))
151				showgid(atoi(*argv));
152			else
153				showgrpname(*argv);
154		}
155		exit(0);
156	}
157}
158
159usage()
160{
161
162	fprintf(stderr, "%s\n%s\n%s\n",
163		"Usage: quota [-guqv]",
164		"\tquota [-qv] -u username ...",
165		"\tquota [-qv] -g groupname ...");
166	exit(1);
167}
168
169/*
170 * Print out quotas for a specified user identifier.
171 */
172showuid(uid)
173	u_long uid;
174{
175	struct passwd *pwd = getpwuid(uid);
176	u_long myuid;
177	char *name;
178
179	if (pwd == NULL)
180		name = "(no account)";
181	else
182		name = pwd->pw_name;
183	myuid = getuid();
184	if (uid != myuid && myuid != 0) {
185		printf("quota: %s (uid %lu): permission denied\n", name, uid);
186		return;
187	}
188	showquotas(USRQUOTA, uid, name);
189}
190
191/*
192 * Print out quotas for a specifed user name.
193 */
194showusrname(name)
195	char *name;
196{
197	struct passwd *pwd = getpwnam(name);
198	u_long myuid;
199
200	if (pwd == NULL) {
201		fprintf(stderr, "quota: %s: unknown user\n", name);
202		return;
203	}
204	myuid = getuid();
205	if (pwd->pw_uid != myuid && myuid != 0) {
206		fprintf(stderr, "quota: %s (uid %u): permission denied\n",
207		    name, pwd->pw_uid);
208		return;
209	}
210	showquotas(USRQUOTA, pwd->pw_uid, name);
211}
212
213/*
214 * Print out quotas for a specified group identifier.
215 */
216showgid(gid)
217	u_long gid;
218{
219	struct group *grp = getgrgid(gid);
220	int ngroups;
221	gid_t mygid, gidset[NGROUPS];
222	register int i;
223	char *name;
224
225	if (grp == NULL)
226		name = "(no entry)";
227	else
228		name = grp->gr_name;
229	mygid = getgid();
230	ngroups = getgroups(NGROUPS, gidset);
231	if (ngroups < 0) {
232		perror("quota: getgroups");
233		return;
234	}
235	if (gid != mygid) {
236		for (i = 0; i < ngroups; i++)
237			if (gid == gidset[i])
238				break;
239		if (i >= ngroups && getuid() != 0) {
240			fprintf(stderr,
241			    "quota: %s (gid %lu): permission denied\n",
242			    name, gid);
243			return;
244		}
245	}
246	showquotas(GRPQUOTA, gid, name);
247}
248
249/*
250 * Print out quotas for a specifed group name.
251 */
252showgrpname(name)
253	char *name;
254{
255	struct group *grp = getgrnam(name);
256	int ngroups;
257	gid_t mygid, gidset[NGROUPS];
258	register int i;
259
260	if (grp == NULL) {
261		fprintf(stderr, "quota: %s: unknown group\n", name);
262		return;
263	}
264	mygid = getgid();
265	ngroups = getgroups(NGROUPS, gidset);
266	if (ngroups < 0) {
267		perror("quota: getgroups");
268		return;
269	}
270	if (grp->gr_gid != mygid) {
271		for (i = 0; i < ngroups; i++)
272			if (grp->gr_gid == gidset[i])
273				break;
274		if (i >= ngroups && getuid() != 0) {
275			fprintf(stderr,
276			    "quota: %s (gid %u): permission denied\n",
277			    name, grp->gr_gid);
278			return;
279		}
280	}
281	showquotas(GRPQUOTA, grp->gr_gid, name);
282}
283
284showquotas(type, id, name)
285	int type;
286	u_long id;
287	char *name;
288{
289	register struct quotause *qup;
290	struct quotause *quplist;
291	char *msgi, *msgb, *nam;
292	int myuid, fd, lines = 0;
293	static int first;
294	static time_t now;
295
296	if (now == 0)
297		time(&now);
298	quplist = getprivs(id, type);
299	for (qup = quplist; qup; qup = qup->next) {
300		if (!vflag &&
301		    qup->dqblk.dqb_isoftlimit == 0 &&
302		    qup->dqblk.dqb_ihardlimit == 0 &&
303		    qup->dqblk.dqb_bsoftlimit == 0 &&
304		    qup->dqblk.dqb_bhardlimit == 0)
305			continue;
306		msgi = (char *)0;
307		if (qup->dqblk.dqb_ihardlimit &&
308		    qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit)
309			msgi = "File limit reached on";
310		else if (qup->dqblk.dqb_isoftlimit &&
311		    qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit)
312			if (qup->dqblk.dqb_itime > now)
313				msgi = "In file grace period on";
314			else
315				msgi = "Over file quota on";
316		msgb = (char *)0;
317		if (qup->dqblk.dqb_bhardlimit &&
318		    qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit)
319			msgb = "Block limit reached on";
320		else if (qup->dqblk.dqb_bsoftlimit &&
321		    qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bsoftlimit)
322			if (qup->dqblk.dqb_btime > now)
323				msgb = "In block grace period on";
324			else
325				msgb = "Over block quota on";
326		if (qflag) {
327			if ((msgi != (char *)0 || msgb != (char *)0) &&
328			    lines++ == 0)
329				heading(type, id, name, "");
330			if (msgi != (char *)0)
331				printf("\t%s %s\n", msgi, qup->fsname);
332			if (msgb != (char *)0)
333				printf("\t%s %s\n", msgb, qup->fsname);
334			continue;
335		}
336		if (vflag ||
337		    qup->dqblk.dqb_curblocks ||
338		    qup->dqblk.dqb_curinodes) {
339			if (lines++ == 0)
340				heading(type, id, name, "");
341			nam = qup->fsname;
342			if (strlen(qup->fsname) > 15) {
343				printf("%s\n", qup->fsname);
344				nam = "";
345			}
346			printf("%15s%8lu%c%7lu%8lu%8s"
347				, nam
348				, (u_long) (dbtob(qup->dqblk.dqb_curblocks)
349					    / 1024)
350				, (msgb == (char *)0) ? ' ' : '*'
351				, (u_long) (dbtob(qup->dqblk.dqb_bsoftlimit)
352					    / 1024)
353				, (u_long) (dbtob(qup->dqblk.dqb_bhardlimit)
354					    / 1024)
355				, (msgb == (char *)0) ? ""
356				    :timeprt(qup->dqblk.dqb_btime));
357			printf("%8lu%c%7lu%8lu%8s\n"
358				, qup->dqblk.dqb_curinodes
359				, (msgi == (char *)0) ? ' ' : '*'
360				, qup->dqblk.dqb_isoftlimit
361				, qup->dqblk.dqb_ihardlimit
362				, (msgi == (char *)0) ? ""
363				    : timeprt(qup->dqblk.dqb_itime)
364			);
365			continue;
366		}
367	}
368	if (!qflag && lines == 0)
369		heading(type, id, name, "none");
370}
371
372heading(type, id, name, tag)
373	int type;
374	u_long id;
375	char *name, *tag;
376{
377
378	printf("Disk quotas for %s %s (%cid %lu): %s\n", qfextension[type],
379	    name, *qfextension[type], id, tag);
380	if (!qflag && tag[0] == '\0') {
381		printf("%15s%8s %7s%8s%8s%8s %7s%8s%8s\n"
382			, "Filesystem"
383			, "blocks"
384			, "quota"
385			, "limit"
386			, "grace"
387			, "files"
388			, "quota"
389			, "limit"
390			, "grace"
391		);
392	}
393}
394
395/*
396 * Calculate the grace period and return a printable string for it.
397 */
398char *
399timeprt(seconds)
400	time_t seconds;
401{
402	time_t hours, minutes;
403	static char buf[20];
404	static time_t now;
405
406	if (now == 0)
407		time(&now);
408	if (now > seconds)
409		return ("none");
410	seconds -= now;
411	minutes = (seconds + 30) / 60;
412	hours = (minutes + 30) / 60;
413	if (hours >= 36) {
414		sprintf(buf, "%lddays", (hours + 12) / 24);
415		return (buf);
416	}
417	if (minutes >= 60) {
418		sprintf(buf, "%2ld:%ld", minutes / 60, minutes % 60);
419		return (buf);
420	}
421	sprintf(buf, "%2ld", minutes);
422	return (buf);
423}
424
425/*
426 * Collect the requested quota information.
427 */
428struct quotause *
429getprivs(id, quotatype)
430	register long id;
431	int quotatype;
432{
433	register struct quotause *qup, *quptail;
434	register struct fstab *fs;
435	struct quotause *quphead;
436	struct statfs *fst;
437	int nfst, i;
438
439	qup = quphead = (struct quotause *)0;
440
441	nfst = getmntinfo(&fst, MNT_WAIT);
442	if (nfst == 0) {
443		fprintf(stderr, "quota: no filesystems mounted!\n");
444		exit(2);
445	}
446	setfsent();
447	for (i=0; i<nfst; i++) {
448		if (qup == NULL) {
449			if ((qup = (struct quotause *)malloc(sizeof *qup))
450			    == NULL) {
451				fprintf(stderr, "quota: out of memory\n");
452				exit(2);
453			}
454		}
455		if (fst[i].f_type == MOUNT_NFS) {
456			if (getnfsquota(&fst[i], NULL, qup, id, quotatype)
457			    == 0)
458				continue;
459		} else if (fst[i].f_type == MOUNT_UFS) {
460			/*
461			 * XXX
462			 * UFS filesystems must be in /etc/fstab, and must
463			 * indicate that they have quotas on (?!) This is quite
464			 * unlike SunOS where quotas can be enabled/disabled
465			 * on a filesystem independent of /etc/fstab, and it
466			 * will still print quotas for them.
467			 */
468			if ((fs = getfsspec(fst[i].f_mntfromname)) == NULL)
469				continue;
470			if (getufsquota(&fst[i], fs, qup, id, quotatype) == 0)
471				continue;
472		} else
473			continue;
474		strcpy(qup->fsname, fst[i].f_mntonname);
475		if (quphead == NULL)
476			quphead = qup;
477		else
478			quptail->next = qup;
479		quptail = qup;
480		quptail->next = 0;
481		qup = NULL;
482	}
483	if (qup)
484		free(qup);
485	endfsent();
486	return (quphead);
487}
488
489/*
490 * Check to see if a particular quota is to be enabled.
491 */
492ufshasquota(fs, type, qfnamep)
493	register struct fstab *fs;
494	int type;
495	char **qfnamep;
496{
497	static char initname, usrname[100], grpname[100];
498	static char buf[BUFSIZ];
499	char *opt, *cp;
500
501	if (!initname) {
502		sprintf(usrname, "%s%s", qfextension[USRQUOTA], qfname);
503		sprintf(grpname, "%s%s", qfextension[GRPQUOTA], qfname);
504		initname = 1;
505	}
506	strcpy(buf, fs->fs_mntops);
507	for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
508		if (cp = index(opt, '='))
509			*cp++ = '\0';
510		if (type == USRQUOTA && strcmp(opt, usrname) == 0)
511			break;
512		if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
513			break;
514	}
515	if (!opt)
516		return (0);
517	if (cp) {
518		*qfnamep = cp;
519		return (1);
520	}
521	(void) sprintf(buf, "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
522	*qfnamep = buf;
523	return (1);
524}
525
526int
527getufsquota(fst, fs, qup, id, quotatype)
528	struct statfs *fst;
529	struct fstab *fs;
530	struct quotause *qup;
531	long id;
532	int quotatype;
533{
534	char *qfpathname;
535	int fd, qcmd;
536
537	qcmd = QCMD(Q_GETQUOTA, quotatype);
538	if (!ufshasquota(fs, quotatype, &qfpathname))
539		return (0);
540
541	if (quotactl(fs->fs_file, qcmd, id, &qup->dqblk) != 0) {
542		if ((fd = open(qfpathname, O_RDONLY)) < 0) {
543			perror(qfpathname);
544			return (0);
545		}
546		(void) lseek(fd, (off_t)(id * sizeof(struct dqblk)), L_SET);
547		switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) {
548		case 0:				/* EOF */
549			/*
550			 * Convert implicit 0 quota (EOF)
551			 * into an explicit one (zero'ed dqblk)
552			 */
553			bzero((caddr_t)&qup->dqblk, sizeof(struct dqblk));
554			break;
555		case sizeof(struct dqblk):	/* OK */
556			break;
557		default:		/* ERROR */
558			fprintf(stderr, "quota: read error");
559			perror(qfpathname);
560			close(fd);
561			return (0);
562		}
563		close(fd);
564	}
565	return (1);
566}
567
568int
569getnfsquota(fst, fs, qup, id, quotatype)
570	struct statfs *fst;
571	struct fstab *fs;
572	struct quotause *qup;
573	long id;
574	int quotatype;
575{
576	struct getquota_args gq_args;
577	struct getquota_rslt gq_rslt;
578	struct dqblk *dqp = &qup->dqblk;
579	struct timeval tv;
580	char *cp;
581
582	if (fst->f_flags & MNT_LOCAL)
583		return (0);
584
585	/*
586	 * rpc.rquotad does not support group quotas
587	 */
588	if (quotatype != USRQUOTA)
589		return (0);
590
591	/*
592	 * must be some form of "hostname:/path"
593	 */
594	cp = strchr(fst->f_mntfromname, ':');
595	if (cp == NULL) {
596		fprintf(stderr, "cannot find hostname for %s\n",
597		    fst->f_mntfromname);
598		return (0);
599	}
600
601	*cp = '\0';
602	if (*(cp+1) != '/') {
603		*cp = ':';
604		return (0);
605	}
606
607	gq_args.gqa_pathp = cp + 1;
608	gq_args.gqa_uid = id;
609	if (callaurpc(fst->f_mntfromname, RQUOTAPROG, RQUOTAVERS,
610	    RQUOTAPROC_GETQUOTA, xdr_getquota_args, &gq_args,
611	    xdr_getquota_rslt, &gq_rslt) != 0) {
612		*cp = ':';
613		return (0);
614	}
615
616	switch (gq_rslt.status) {
617	case Q_NOQUOTA:
618		break;
619	case Q_EPERM:
620		fprintf(stderr, "quota permission error, host: %s\n",
621			fst->f_mntfromname);
622		break;
623	case Q_OK:
624		gettimeofday(&tv, NULL);
625			/* blocks*/
626		dqp->dqb_bhardlimit =
627		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit *
628		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE;
629		dqp->dqb_bsoftlimit =
630		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit *
631		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE;
632		dqp->dqb_curblocks =
633		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks *
634		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE;
635			/* inodes */
636		dqp->dqb_ihardlimit =
637			gq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit;
638		dqp->dqb_isoftlimit =
639			gq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit;
640		dqp->dqb_curinodes =
641			gq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles;
642			/* grace times */
643		dqp->dqb_btime =
644		    tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft;
645		dqp->dqb_itime =
646		    tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft;
647		*cp = ':';
648		return (1);
649	default:
650		fprintf(stderr, "bad rpc result, host: %s\n",
651		    fst->f_mntfromname);
652		break;
653	}
654	*cp = ':';
655	return (0);
656}
657
658int
659callaurpc(host, prognum, versnum, procnum, inproc, in, outproc, out)
660	char *host;
661	xdrproc_t inproc, outproc;
662	char *in, *out;
663{
664	struct sockaddr_in server_addr;
665	enum clnt_stat clnt_stat;
666	struct hostent *hp;
667	struct timeval timeout, tottimeout;
668
669	CLIENT *client = NULL;
670	int socket = RPC_ANYSOCK;
671
672	if ((hp = gethostbyname(host)) == NULL)
673		return ((int) RPC_UNKNOWNHOST);
674	timeout.tv_usec = 0;
675	timeout.tv_sec = 6;
676	bcopy(hp->h_addr, &server_addr.sin_addr, hp->h_length);
677	server_addr.sin_family = AF_INET;
678	server_addr.sin_port =  0;
679
680	if ((client = clntudp_create(&server_addr, prognum,
681	    versnum, timeout, &socket)) == NULL)
682		return ((int) rpc_createerr.cf_stat);
683
684	client->cl_auth = authunix_create_default();
685	tottimeout.tv_sec = 25;
686	tottimeout.tv_usec = 0;
687	clnt_stat = clnt_call(client, procnum, inproc, in,
688	    outproc, out, tottimeout);
689
690	return ((int) clnt_stat);
691}
692
693alldigits(s)
694	register char *s;
695{
696	register c;
697
698	c = *s++;
699	do {
700		if (!isdigit(c))
701			return (0);
702	} while (c = *s++);
703	return (1);
704}
705