quota.c revision 8327
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[] = "@(#)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/file.h>
52#include <sys/stat.h>
53#include <ufs/ufs/quota.h>
54#include <stdio.h>
55#include <fstab.h>
56#include <ctype.h>
57#include <pwd.h>
58#include <grp.h>
59#include <errno.h>
60
61char *qfname = QUOTAFILENAME;
62char *qfextension[] = INITQFNAMES;
63
64struct quotause {
65	struct	quotause *next;
66	long	flags;
67	struct	dqblk dqblk;
68	char	fsname[MAXPATHLEN + 1];
69} *getprivs();
70#define	FOUND	0x01
71
72int	qflag;
73int	vflag;
74
75main(argc, argv)
76	char *argv[];
77{
78	int ngroups, gidset[NGROUPS];
79	int i, gflag = 0, uflag = 0;
80	char ch;
81	extern char *optarg;
82	extern int optind, errno;
83
84	if (quotactl("/", 0, 0, (caddr_t)0) < 0 && errno == EOPNOTSUPP) {
85		fprintf(stderr, "There are no quotas on this system\n");
86		exit(0);
87	}
88	while ((ch = getopt(argc, argv, "ugvq")) != EOF) {
89		switch(ch) {
90		case 'g':
91			gflag++;
92			break;
93		case 'u':
94			uflag++;
95			break;
96		case 'v':
97			vflag++;
98			break;
99		case 'q':
100			qflag++;
101			break;
102		default:
103			usage();
104		}
105	}
106	argc -= optind;
107	argv += optind;
108	if (!uflag && !gflag)
109		uflag++;
110	if (argc == 0) {
111		if (uflag)
112			showuid(getuid());
113		if (gflag) {
114			ngroups = getgroups(NGROUPS, gidset);
115			if (ngroups < 0) {
116				perror("quota: getgroups");
117				exit(1);
118			}
119			for (i = 1; i < ngroups; i++)
120				showgid(gidset[i]);
121		}
122		exit(0);
123	}
124	if (uflag && gflag)
125		usage();
126	if (uflag) {
127		for (; argc > 0; argc--, argv++) {
128			if (alldigits(*argv))
129				showuid(atoi(*argv));
130			else
131				showusrname(*argv);
132		}
133		exit(0);
134	}
135	if (gflag) {
136		for (; argc > 0; argc--, argv++) {
137			if (alldigits(*argv))
138				showgid(atoi(*argv));
139			else
140				showgrpname(*argv);
141		}
142		exit(0);
143	}
144}
145
146usage()
147{
148
149	fprintf(stderr, "%s\n%s\n%s\n",
150		"Usage: quota [-guqv]",
151		"\tquota [-qv] -u username ...",
152		"\tquota [-qv] -g groupname ...");
153	exit(1);
154}
155
156/*
157 * Print out quotas for a specified user identifier.
158 */
159showuid(uid)
160	u_long uid;
161{
162	struct passwd *pwd = getpwuid(uid);
163	u_long myuid;
164	char *name;
165
166	if (pwd == NULL)
167		name = "(no account)";
168	else
169		name = pwd->pw_name;
170	myuid = getuid();
171	if (uid != myuid && myuid != 0) {
172		printf("quota: %s (uid %lu): permission denied\n", name, uid);
173		return;
174	}
175	showquotas(USRQUOTA, uid, name);
176}
177
178/*
179 * Print out quotas for a specifed user name.
180 */
181showusrname(name)
182	char *name;
183{
184	struct passwd *pwd = getpwnam(name);
185	u_long myuid;
186
187	if (pwd == NULL) {
188		fprintf(stderr, "quota: %s: unknown user\n", name);
189		return;
190	}
191	myuid = getuid();
192	if (pwd->pw_uid != myuid && myuid != 0) {
193		fprintf(stderr, "quota: %s (uid %d): permission denied\n",
194		    name, pwd->pw_uid);
195		return;
196	}
197	showquotas(USRQUOTA, pwd->pw_uid, name);
198}
199
200/*
201 * Print out quotas for a specified group identifier.
202 */
203showgid(gid)
204	u_long gid;
205{
206	struct group *grp = getgrgid(gid);
207	int ngroups, gidset[NGROUPS];
208	register int i;
209	char *name;
210
211	if (grp == NULL)
212		name = "(no entry)";
213	else
214		name = grp->gr_name;
215	ngroups = getgroups(NGROUPS, gidset);
216	if (ngroups < 0) {
217		perror("quota: getgroups");
218		return;
219	}
220	for (i = 1; i < ngroups; i++)
221		if (gid == gidset[i])
222			break;
223	if (i >= ngroups && getuid() != 0) {
224		fprintf(stderr, "quota: %s (gid %lu): permission denied\n",
225		    name, gid);
226		return;
227	}
228	showquotas(GRPQUOTA, gid, name);
229}
230
231/*
232 * Print out quotas for a specifed group name.
233 */
234showgrpname(name)
235	char *name;
236{
237	struct group *grp = getgrnam(name);
238	int ngroups, gidset[NGROUPS];
239	register int i;
240
241	if (grp == NULL) {
242		fprintf(stderr, "quota: %s: unknown group\n", name);
243		return;
244	}
245	ngroups = getgroups(NGROUPS, gidset);
246	if (ngroups < 0) {
247		perror("quota: getgroups");
248		return;
249	}
250	for (i = 1; i < ngroups; i++)
251		if (grp->gr_gid == gidset[i])
252			break;
253	if (i >= ngroups && getuid() != 0) {
254		fprintf(stderr, "quota: %s (gid %d): permission denied\n",
255		    name, grp->gr_gid);
256		return;
257	}
258	showquotas(GRPQUOTA, grp->gr_gid, name);
259}
260
261showquotas(type, id, name)
262	int type;
263	u_long id;
264	char *name;
265{
266	register struct quotause *qup;
267	struct quotause *quplist, *getprivs();
268	char *msgi, *msgb, *timeprt();
269	int myuid, fd, lines = 0;
270	static int first;
271	static time_t now;
272
273	if (now == 0)
274		time(&now);
275	quplist = getprivs(id, type);
276	for (qup = quplist; qup; qup = qup->next) {
277		if (!vflag &&
278		    qup->dqblk.dqb_isoftlimit == 0 &&
279		    qup->dqblk.dqb_ihardlimit == 0 &&
280		    qup->dqblk.dqb_bsoftlimit == 0 &&
281		    qup->dqblk.dqb_bhardlimit == 0)
282			continue;
283		msgi = (char *)0;
284		if (qup->dqblk.dqb_ihardlimit &&
285		    qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit)
286			msgi = "File limit reached on";
287		else if (qup->dqblk.dqb_isoftlimit &&
288		    qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit)
289			if (qup->dqblk.dqb_itime > now)
290				msgi = "In file grace period on";
291			else
292				msgi = "Over file quota on";
293		msgb = (char *)0;
294		if (qup->dqblk.dqb_bhardlimit &&
295		    qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit)
296			msgb = "Block limit reached on";
297		else if (qup->dqblk.dqb_bsoftlimit &&
298		    qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bsoftlimit)
299			if (qup->dqblk.dqb_btime > now)
300				msgb = "In block grace period on";
301			else
302				msgb = "Over block quota on";
303		if (qflag) {
304			if ((msgi != (char *)0 || msgb != (char *)0) &&
305			    lines++ == 0)
306				heading(type, id, name, "");
307			if (msgi != (char *)0)
308				printf("\t%s %s\n", msgi, qup->fsname);
309			if (msgb != (char *)0)
310				printf("\t%s %s\n", msgb, qup->fsname);
311			continue;
312		}
313		if (vflag ||
314		    qup->dqblk.dqb_curblocks ||
315		    qup->dqblk.dqb_curinodes) {
316			if (lines++ == 0)
317				heading(type, id, name, "");
318			printf("%15s%8lu%c%7lu%8lu%8s"
319				, qup->fsname
320				, (u_long)
321				  (dbtob(qup->dqblk.dqb_curblocks) / 1024)
322				, (msgb == (char *)0) ? ' ' : '*'
323				, (u_long)
324				  (dbtob(qup->dqblk.dqb_bsoftlimit) / 1024)
325				, (u_long)
326				  (dbtob(qup->dqblk.dqb_bhardlimit) / 1024)
327				, (msgb == (char *)0) ? ""
328				    : timeprt(qup->dqblk.dqb_btime));
329			printf("%8lu%c%7lu%8lu%8s\n"
330				, qup->dqblk.dqb_curinodes
331				, (msgi == (char *)0) ? ' ' : '*'
332				, qup->dqblk.dqb_isoftlimit
333				, qup->dqblk.dqb_ihardlimit
334				, (msgi == (char *)0) ? ""
335				    : timeprt(qup->dqblk.dqb_itime)
336			);
337			continue;
338		}
339	}
340	if (!qflag && lines == 0)
341		heading(type, id, name, "none");
342}
343
344heading(type, id, name, tag)
345	int type;
346	u_long id;
347	char *name, *tag;
348{
349
350	printf("Disk quotas for %s %s (%cid %lu): %s\n", qfextension[type],
351	    name, *qfextension[type], id, tag);
352	if (!qflag && tag[0] == '\0') {
353		printf("%15s%8s %7s%8s%8s%8s %7s%8s%8s\n"
354			, "Filesystem"
355			, "blocks"
356			, "quota"
357			, "limit"
358			, "grace"
359			, "files"
360			, "quota"
361			, "limit"
362			, "grace"
363		);
364	}
365}
366
367/*
368 * Calculate the grace period and return a printable string for it.
369 */
370char *
371timeprt(seconds)
372	time_t seconds;
373{
374	time_t hours, minutes;
375	static char buf[20];
376	static time_t now;
377
378	if (now == 0)
379		time(&now);
380	if (now > seconds)
381		return ("none");
382	seconds -= now;
383	minutes = (seconds + 30) / 60;
384	hours = (minutes + 30) / 60;
385	if (hours >= 36) {
386		sprintf(buf, "%lddays", (hours + 12) / 24);
387		return (buf);
388	}
389	if (minutes >= 60) {
390		sprintf(buf, "%2ld:%ld", minutes / 60, minutes % 60);
391		return (buf);
392	}
393	sprintf(buf, "%2ld", minutes);
394	return (buf);
395}
396
397/*
398 * Collect the requested quota information.
399 */
400struct quotause *
401getprivs(id, quotatype)
402	register long id;
403	int quotatype;
404{
405	register struct fstab *fs;
406	register struct quotause *qup, *quptail;
407	struct quotause *quphead;
408	char *qfpathname;
409	int qcmd, fd;
410
411	setfsent();
412	quphead = (struct quotause *)0;
413	qcmd = QCMD(Q_GETQUOTA, quotatype);
414	while (fs = getfsent()) {
415		if (strcmp(fs->fs_vfstype, "ufs"))
416			continue;
417		if (!hasquota(fs, quotatype, &qfpathname))
418			continue;
419		if ((qup = (struct quotause *)malloc(sizeof *qup)) == NULL) {
420			fprintf(stderr, "quota: out of memory\n");
421			exit(2);
422		}
423		if (quotactl(fs->fs_file, qcmd, id, &qup->dqblk) != 0) {
424			if ((fd = open(qfpathname, O_RDONLY)) < 0) {
425				perror(qfpathname);
426				free(qup);
427				continue;
428			}
429			lseek(fd, (long)(id * sizeof(struct dqblk)), L_SET);
430			switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) {
431			case 0:			/* EOF */
432				/*
433				 * Convert implicit 0 quota (EOF)
434				 * into an explicit one (zero'ed dqblk)
435				 */
436				bzero((caddr_t)&qup->dqblk,
437				    sizeof(struct dqblk));
438				break;
439
440			case sizeof(struct dqblk):	/* OK */
441				break;
442
443			default:		/* ERROR */
444				fprintf(stderr, "quota: read error");
445				perror(qfpathname);
446				close(fd);
447				free(qup);
448				continue;
449			}
450			close(fd);
451		}
452		strcpy(qup->fsname, fs->fs_file);
453		if (quphead == NULL)
454			quphead = qup;
455		else
456			quptail->next = qup;
457		quptail = qup;
458		qup->next = 0;
459	}
460	endfsent();
461	return (quphead);
462}
463
464/*
465 * Check to see if a particular quota is to be enabled.
466 */
467hasquota(fs, type, qfnamep)
468	register struct fstab *fs;
469	int type;
470	char **qfnamep;
471{
472	register char *opt;
473	char *cp, *index(), *strtok();
474	static char initname, usrname[100], grpname[100];
475	static char buf[BUFSIZ];
476
477	if (!initname) {
478		sprintf(usrname, "%s%s", qfextension[USRQUOTA], qfname);
479		sprintf(grpname, "%s%s", qfextension[GRPQUOTA], qfname);
480		initname = 1;
481	}
482	strcpy(buf, fs->fs_mntops);
483	for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
484		if (cp = index(opt, '='))
485			*cp++ = '\0';
486		if (type == USRQUOTA && strcmp(opt, usrname) == 0)
487			break;
488		if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
489			break;
490	}
491	if (!opt)
492		return (0);
493	if (cp) {
494		*qfnamep = cp;
495		return (1);
496	}
497	(void) sprintf(buf, "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
498	*qfnamep = buf;
499	return (1);
500}
501
502alldigits(s)
503	register char *s;
504{
505	register c;
506
507	c = *s++;
508	do {
509		if (!isdigit(c))
510			return (0);
511	} while (c = *s++);
512	return (1);
513}
514