1/*
2 * Copyright (c) 2002-2005 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23/*
24 * Copyright (c) 1980, 1990, 1993
25 *	The Regents of the University of California.  All rights reserved.
26 *
27 * This code is derived from software contributed to Berkeley by
28 * Robert Elz at The University of Melbourne.
29 *
30 * Redistribution and use in source and binary forms, with or without
31 * modification, are permitted provided that the following conditions
32 * are met:
33 * 1. Redistributions of source code must retain the above copyright
34 *    notice, this list of conditions and the following disclaimer.
35 * 2. Redistributions in binary form must reproduce the above copyright
36 *    notice, this list of conditions and the following disclaimer in the
37 *    documentation and/or other materials provided with the distribution.
38 * 3. All advertising materials mentioning features or use of this software
39 *    must display the following acknowledgement:
40 *	This product includes software developed by the University of
41 *	California, Berkeley and its contributors.
42 * 4. Neither the name of the University nor the names of its contributors
43 *    may be used to endorse or promote products derived from this software
44 *    without specific prior written permission.
45 *
46 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
47 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
48 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
49 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
50 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
51 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
52 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
53 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
54 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
55 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
56 * SUCH DAMAGE.
57 */
58
59#include <sys/cdefs.h>
60
61#ifndef lint
62__unused static char copyright[] =
63"@(#) Copyright (c) 1980, 1990, 1993\n\
64	The Regents of the University of California.  All rights reserved.\n";
65#endif /* not lint */
66
67#ifndef lint
68__unused static char sccsid[] = "@(#)repquota.c	8.2 (Berkeley) 11/22/94";
69#endif /* not lint */
70
71/*
72 * Quota report
73 */
74#include <sys/param.h>
75#include <sys/stat.h>
76#include <sys/queue.h>
77#include <sys/quota.h>
78
79#ifdef __APPLE__
80#include <sys/mount.h>
81#endif /* __APPLE__ */
82#include <errno.h>
83#include <fstab.h>
84#include <grp.h>
85#include <pwd.h>
86#include <stdio.h>
87#include <stdlib.h>
88#include <string.h>
89#include <unistd.h>
90#ifdef __APPLE__
91#include <libkern/OSByteOrder.h>
92#endif /* __APPLE__ */
93
94char *qfname = QUOTAFILENAME;
95char *qfextension[] = INITQFNAMES;
96#ifdef __APPLE__
97u_int32_t quotamagic[MAXQUOTAS] = INITQMAGICS;
98#endif /* __APPLE__ */
99
100#ifndef __APPLE__
101struct fileusage {
102	struct	fileusage *fu_next;
103	struct	dqblk fu_dqblk;
104	u_long	fu_id;
105	char	fu_name[1];
106	/* actually bigger */
107};
108struct fileusage * addid(u_long, int);
109struct fileusage * lookup(u_long, int);
110
111#define FUHASH 1024	/* must be power of two */
112struct fileusage *fuhead[MAXQUOTAS][FUHASH];
113u_long highid[MAXQUOTAS];	/* highest addid()'ed identifier per type */
114#endif /* NOT __APPLE__ */
115
116int	vflag;			/* verbose */
117int	aflag;			/* all file systems */
118
119int	hasquota(struct statfs *, int, char **);
120int	repquota(struct statfs *, int, char *);
121int	oneof(char *, char **, int);
122void	usage(void);
123
124int
125main(argc, argv)
126	int argc;
127	char **argv;
128{
129#ifndef __APPLE__
130	register struct fstab *fs;
131	register struct passwd *pw;
132	register struct group *gr;
133#endif /* __APPLE__ */
134	int gflag = 0, uflag = 0, errs = 0;
135	long i, argnum, done = 0;
136#ifdef __APPLE__
137        int nfst;
138        struct statfs *fst;
139#endif /* __APPLE__ */
140	char ch, *qfnp;
141
142	while ((ch = getopt(argc, argv, "aguv")) != EOF) {
143		switch(ch) {
144		case 'a':
145			aflag++;
146			break;
147		case 'g':
148			gflag++;
149			break;
150		case 'u':
151			uflag++;
152			break;
153		case 'v':
154			vflag++;
155			break;
156		default:
157			usage();
158		}
159	}
160	argc -= optind;
161	argv += optind;
162	if (argc == 0 && !aflag)
163		usage();
164	if (!gflag && !uflag) {
165		if (aflag)
166			gflag++;
167		uflag++;
168	}
169
170#ifdef __APPLE__
171        nfst = getmntinfo(&fst, MNT_WAIT);
172        if (nfst==0) {
173          fprintf(stderr, "repquota: no filesystems mounted");
174          return(1);
175        }
176
177		for (i=0; i<nfst; i++) {
178			if(strcmp(fst[i].f_fstypename, "hfs")) {
179				continue;
180			}
181			if (aflag) {
182				if (gflag && hasquota(&fst[i], GRPQUOTA, &qfnp))
183					errs += repquota(&fst[i], GRPQUOTA, qfnp);
184				if (uflag && hasquota(&fst[i], USRQUOTA, &qfnp))
185					errs += repquota(&fst[i], USRQUOTA, qfnp);
186				continue;
187			}
188			if ((argnum = oneof(fst[i].f_mntonname, argv, argc)) >= 0 ||
189					(argnum = oneof(fst[i].f_mntfromname, argv, argc)) >= 0) {
190				done |= 1 << argnum;
191				if (gflag && hasquota(&fst[i], GRPQUOTA, &qfnp))
192					errs += repquota(&fst[i], GRPQUOTA, qfnp);
193				if (uflag && hasquota(&fst[i], USRQUOTA, &qfnp))
194					errs += repquota(&fst[i], USRQUOTA, qfnp);
195			}
196		}
197#else
198	if (gflag) {
199		setgrent();
200		while ((gr = getgrent()) != 0)
201			(void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name);
202		endgrent();
203	}
204	if (uflag) {
205		setpwent();
206		while ((pw = getpwent()) != 0)
207			(void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name);
208		endpwent();
209	}
210
211	setfsent();
212	while ((fs = getfsent()) != NULL) {
213		if (strcmp(fs->fs_vfstype, "ufs"))
214			continue;
215		if (aflag) {
216			if (gflag && hasquota(fs, GRPQUOTA, &qfnp))
217				errs += repquota(fs, GRPQUOTA, qfnp);
218			if (uflag && hasquota(fs, USRQUOTA, &qfnp))
219				errs += repquota(fs, USRQUOTA, qfnp);
220			continue;
221		}
222		if ((argnum = oneof(fs->fs_file, argv, argc)) >= 0 ||
223		    (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) {
224			done |= 1 << argnum;
225			if (gflag && hasquota(fs, GRPQUOTA, &qfnp))
226				errs += repquota(fs, GRPQUOTA, qfnp);
227			if (uflag && hasquota(fs, USRQUOTA, &qfnp))
228				errs += repquota(fs, USRQUOTA, qfnp);
229		}
230	}
231	endfsent();
232#endif /* __APPLE */
233	for (i = 0; i < argc; i++)
234		if ((done & (1 << i)) == 0)
235			fprintf(stderr, "%s not found in fstab\n", argv[i]);
236	exit(errs);
237}
238
239void
240usage()
241{
242	fprintf(stderr, "Usage:\n\t%s\n\t%s\n",
243		"repquota [-v] [-g] [-u] -a",
244		"repquota [-v] [-g] [-u] filesys ...");
245	exit(1);
246}
247
248#ifdef __APPLE__
249int
250repquota(fst, type, qfpathname)
251        struct statfs *fst;
252	int type;
253	char *qfpathname;
254{
255	FILE *qf;
256	uid_t id;
257	struct dqblk dqbuf;
258	char *timeprt();
259	char *name;
260	struct dqfilehdr dqhdr;
261	static int warned = 0;
262	static int multiple = 0;
263	extern int errno;
264	int i;
265	struct passwd *pw;
266	struct group *gr;
267	int maxentries;
268	u_int64_t bsoftlimit;
269	u_int32_t isoftlimit;
270	u_int64_t curbytes;
271	u_int32_t curinodes;
272
273
274	if (quotactl(fst->f_mntonname, QCMD(Q_SYNC, type), 0, 0) < 0 &&
275	    errno == ENOTSUP && !warned && vflag) {
276		warned++;
277		fprintf(stdout,
278		    "*** Warning: Quotas are not compiled into this kernel\n");
279	}
280	if (multiple++)
281		printf("\n");
282	if (vflag)
283		fprintf(stdout, "*** Report for %s quotas on %s (%s)\n",
284		    qfextension[type], fst->f_mntonname, fst->f_mntfromname);
285	if ((qf = fopen(qfpathname, "r")) == NULL) {
286		perror(qfpathname);
287		return (1);
288	}
289
290	/* Read in the quota file header. */
291	if (fread((char *)&dqhdr, sizeof(struct dqfilehdr), 1, qf) > 0) {
292		/* Check for non big endian file. */
293		if (OSSwapBigToHostInt32(dqhdr.dqh_magic) != quotamagic[type] &&
294		    OSSwapInt32(dqhdr.dqh_magic) == quotamagic[type]) {
295			(void) fprintf(stderr,
296			    "*** Error: %s: not in big endian byte order\n", qfpathname);
297			(void) fclose(qf);
298			return (1);
299		}
300		/* Sanity check the quota file header. */
301		if ((OSSwapBigToHostInt32(dqhdr.dqh_magic) != quotamagic[type]) ||
302		    (OSSwapBigToHostInt32(dqhdr.dqh_version) > QF_VERSION) ||
303		    (!powerof2(OSSwapBigToHostInt32(dqhdr.dqh_maxentries)))) {
304			(void) fprintf(stderr,
305			    "repquota: %s: not a valid quota file\n", qfpathname);
306			(void) fclose(qf);
307			return (1);
308		}
309	}
310
311	printf("                        1K Block limits               File limits\n");
312	printf("User                used        soft        hard  grace    used  soft  hard  grace\n");
313
314	maxentries = OSSwapBigToHostInt32(dqhdr.dqh_maxentries);
315
316	/* Read the entries in the quota file. */
317	for (i = 0; i < maxentries; i++) {
318		if (fread(&dqbuf, sizeof(struct dqblk), 1, qf) == 0 && feof(qf))
319			break;
320		id = OSSwapBigToHostInt32(dqbuf.dqb_id);
321		if (id == 0)
322			continue;
323		if (dqbuf.dqb_curinodes == 0 && dqbuf.dqb_curbytes == 0LL)
324			continue;
325		name = NULL;
326		switch (type) {
327		case USRQUOTA:
328			if ((pw = getpwuid(id)) != 0)
329				name = pw->pw_name;
330			break;
331		case GRPQUOTA:
332			if ((gr = getgrgid(id)) != 0)
333				name = gr->gr_name;
334			break;
335		}
336		if (name)
337			printf("%-10s", name);
338		else
339			printf("%-10u", (unsigned int)id);
340
341		bsoftlimit = OSSwapBigToHostInt64( dqbuf.dqb_bsoftlimit );
342		isoftlimit = OSSwapBigToHostInt32( dqbuf.dqb_isoftlimit );
343		curbytes   = OSSwapBigToHostInt64( dqbuf.dqb_curbytes );
344		curinodes  = OSSwapBigToHostInt32( dqbuf.dqb_curinodes );
345
346		printf("%c%c%12qd%12qd%12qd%7s",
347			bsoftlimit &&
348			    curbytes >=
349			    bsoftlimit ? '+' : '-',
350			isoftlimit &&
351			    curinodes >=
352			    isoftlimit ? '+' : '-',
353			curbytes / 1024,
354			bsoftlimit / 1024,
355			OSSwapBigToHostInt64( dqbuf.dqb_bhardlimit ) / 1024,
356			bsoftlimit &&
357			    curbytes >=
358			    bsoftlimit ?
359			    timeprt(OSSwapBigToHostInt32(dqbuf.dqb_btime)) : "");
360		printf("  %6d%6d%6d%7s\n",
361			curinodes,
362			isoftlimit,
363			OSSwapBigToHostInt32( dqbuf.dqb_ihardlimit ),
364			isoftlimit &&
365			    curinodes >=
366			    isoftlimit ?
367			    timeprt(OSSwapBigToHostInt32(dqbuf.dqb_itime)) : "");
368	}
369	fclose(qf);
370
371	return (0);
372}
373#else
374repquota(fs, type, qfpathname)
375	register struct fstab *fs;
376	int type;
377	char *qfpathname;
378{
379	register struct fileusage *fup;
380	FILE *qf;
381	u_long id;
382	struct dqblk dqbuf;
383	char *timeprt();
384	static struct dqblk zerodqblk;
385	static int warned = 0;
386	static int multiple = 0;
387	extern int errno;
388
389	if (quotactl(fs->fs_file, QCMD(Q_SYNC, type), 0, 0) < 0 &&
390	    errno == ENOTSUP && !warned && vflag) {
391		warned++;
392		fprintf(stdout,
393		    "*** Warning: Quotas are not compiled into this kernel\n");
394	}
395	if (multiple++)
396		printf("\n");
397	if (vflag)
398		fprintf(stdout, "*** Report for %s quotas on %s (%s)\n",
399		    qfextension[type], fs->fs_file, fs->fs_spec);
400	if ((qf = fopen(qfpathname, "r")) == NULL) {
401		perror(qfpathname);
402		return (1);
403	}
404	for (id = 0; ; id++) {
405		fread(&dqbuf, sizeof(struct dqblk), 1, qf);
406		if (feof(qf))
407			break;
408		if (dqbuf.dqb_curinodes == 0 && dqbuf.dqb_curblocks == 0)
409			continue;
410		if ((fup = lookup(id, type)) == 0)
411			fup = addid(id, type, (char *)0);
412		fup->fu_dqblk = dqbuf;
413	}
414	fclose(qf);
415	printf("                        Block limits               File limits\n");
416	printf("User            used    soft    hard  grace    used  soft  hard  grace\n");
417
418	for (id = 0; id <= highid[type]; id++) {
419		fup = lookup(id, type);
420		if (fup == 0)
421			continue;
422		if (fup->fu_dqblk.dqb_curinodes == 0 &&
423		    fup->fu_dqblk.dqb_curblocks == 0)
424			continue;
425		printf("%-10s", fup->fu_name);
426		printf("%c%c%8d%8d%8d%7s",
427			fup->fu_dqblk.dqb_bsoftlimit &&
428			    fup->fu_dqblk.dqb_curblocks >=
429			    fup->fu_dqblk.dqb_bsoftlimit ? '+' : '-',
430			fup->fu_dqblk.dqb_isoftlimit &&
431			    fup->fu_dqblk.dqb_curinodes >=
432			    fup->fu_dqblk.dqb_isoftlimit ? '+' : '-',
433			dbtob(fup->fu_dqblk.dqb_curblocks) / 1024,
434			dbtob(fup->fu_dqblk.dqb_bsoftlimit) / 1024,
435			dbtob(fup->fu_dqblk.dqb_bhardlimit) / 1024,
436			fup->fu_dqblk.dqb_bsoftlimit &&
437			    fup->fu_dqblk.dqb_curblocks >=
438			    fup->fu_dqblk.dqb_bsoftlimit ?
439			    timeprt(fup->fu_dqblk.dqb_btime) : "");
440		printf("  %6d%6d%6d%7s\n",
441			fup->fu_dqblk.dqb_curinodes,
442			fup->fu_dqblk.dqb_isoftlimit,
443			fup->fu_dqblk.dqb_ihardlimit,
444			fup->fu_dqblk.dqb_isoftlimit &&
445			    fup->fu_dqblk.dqb_curinodes >=
446			    fup->fu_dqblk.dqb_isoftlimit ?
447			    timeprt(fup->fu_dqblk.dqb_itime) : "");
448		fup->fu_dqblk = zerodqblk;
449	}
450	return (0);
451}
452#endif /* __APPLE */
453
454/*
455 * Check to see if target appears in list of size cnt.
456 */
457int
458oneof(target, list, cnt)
459	register char *target, **list;
460	int cnt;
461{
462	register int i;
463
464	for (i = 0; i < cnt; i++)
465		if (strcmp(target, list[i]) == 0)
466			return (i);
467	return (-1);
468}
469
470/*
471 * Check to see if a particular quota is to be enabled.
472 */
473#ifdef __APPLE__
474int
475hasquota(fst, type, qfnamep)
476        register struct statfs *fst;
477	int type;
478	char **qfnamep;
479{
480        struct stat sb;
481	static char initname, usrname[100], grpname[100];
482	static char buf[BUFSIZ];
483
484	if (!initname) {
485		snprintf(usrname, sizeof(usrname), "%s%s", qfextension[USRQUOTA], qfname);
486		snprintf(grpname, sizeof(grpname), "%s%s", qfextension[GRPQUOTA], qfname);
487		initname = 1;
488	}
489
490        /*
491	  We only support the default path to the
492	  on disk quota files.
493	*/
494
495        (void)snprintf(buf, sizeof(buf), "%s/%s.%s", fst->f_mntonname,
496		      QUOTAOPSNAME, qfextension[type] );
497        if (stat(buf, &sb) != 0) {
498          /* There appears to be no mount option file */
499          return(0);
500        }
501
502	(void) snprintf(buf, sizeof(buf), "%s/%s.%s", fst->f_mntonname, qfname, qfextension[type]);
503	*qfnamep = buf;
504	return (1);
505}
506#else
507hasquota(fs, type, qfnamep)
508	register struct fstab *fs;
509	int type;
510	char **qfnamep;
511{
512	register char *opt;
513	char *cp, *index(), *strtok();
514	static char initname, usrname[100], grpname[100];
515	static char buf[BUFSIZ];
516
517	if (!initname) {
518		snprintf(usrname, sizoef(usrname), "%s%s", qfextension[USRQUOTA], qfname);
519		snprintf(grpname, sizeof(grpname), "%s%s", qfextension[GRPQUOTA], qfname);
520		initname = 1;
521	}
522	strlcpy(buf, fs->fs_mntops, sizeof(buf));
523	for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
524		if (cp = index(opt, '='))
525			*cp++ = '\0';
526		if (type == USRQUOTA && strcmp(opt, usrname) == 0)
527			break;
528		if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
529			break;
530	}
531	if (!opt)
532		return (0);
533	if (cp) {
534		*qfnamep = cp;
535		return (1);
536	}
537	(void) snprintf(buf, sizeof(buf), "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
538	*qfnamep = buf;
539	return (1);
540}
541#endif /* __APPLE__ */
542
543
544#ifndef __APPLE__
545
546/*
547 * Routines to manage the file usage table.
548 *
549 * Lookup an id of a specific type.
550 */
551struct fileusage *
552lookup(id, type)
553	u_long id;
554	int type;
555{
556	register struct fileusage *fup;
557
558	for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next)
559		if (fup->fu_id == id)
560			return (fup);
561	return ((struct fileusage *)0);
562}
563
564/*
565 * Add a new file usage id if it does not already exist.
566 */
567struct fileusage *
568addid(id, type, name)
569	u_long id;
570	int type;
571	char *name;
572{
573	struct fileusage *fup, **fhp;
574	int len;
575	extern char *calloc();
576
577	if (fup = lookup(id, type))
578		return (fup);
579	if (name)
580		len = strlen(name);
581	else
582		len = 10;
583	if ((fup = (struct fileusage *)calloc(1, sizeof(*fup) + len)) == NULL) {
584		fprintf(stderr, "out of memory for fileusage structures\n");
585		exit(1);
586	}
587	fhp = &fuhead[type][id & (FUHASH - 1)];
588	fup->fu_next = *fhp;
589	*fhp = fup;
590	fup->fu_id = id;
591	if (id > highid[type])
592		highid[type] = id;
593	if (name) {
594		bcopy(name, fup->fu_name, len + 1);
595	} else {
596		sprintf(fup->fu_name, "%u", id);
597	}
598	return (fup);
599}
600#endif /* !__APPLE__ */
601
602/*
603 * Calculate the grace period and return a printable string for it.
604 */
605char *
606timeprt(seconds)
607	time_t seconds;
608{
609	time_t hours, minutes;
610	static char buf[20];
611	static time_t now;
612
613	if (now == 0)
614		time(&now);
615	if (now > seconds)
616		return ("none");
617	seconds -= now;
618	minutes = (seconds + 30) / 60;
619	hours = (minutes + 30) / 60;
620	if (hours >= 36) {
621		snprintf(buf, sizeof(buf), "%lddays", (hours + 12) / 24);
622		return (buf);
623	}
624	if (minutes >= 60) {
625		sprintf(buf, "%2ld:%ld", minutes / 60, minutes % 60);
626		return (buf);
627	}
628	sprintf(buf, "%2ld", minutes);
629	return (buf);
630}
631