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. Neither the name of the University nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33/*
34 * Quota report
35 */
36#include <sys/param.h>	/* dbtob */
37#include <sys/stat.h>
38#include <ufs/ufs/quota.h>
39#include <fstab.h>
40#include <pwd.h>
41#include <grp.h>
42#include <stdio.h>
43#include <unistd.h>
44#include <string.h>
45#include <errno.h>
46#include <stdlib.h>
47
48char *qfname = QUOTAFILENAME;
49char *qfextension[] = INITQFNAMES;
50
51struct fileusage {
52	struct	fileusage *fu_next;
53	struct	dqblk fu_dqblk;
54	uid_t	fu_id;
55	char	fu_name[1];
56	/* actually bigger */
57};
58#define FUHASH 1024	/* must be power of two */
59struct fileusage *fuhead[MAXQUOTAS][FUHASH];
60struct fileusage *lookup(uid_t, int);
61struct fileusage *addid(uid_t id, int type, char *name);
62uid_t highid[MAXQUOTAS];	/* highest addid()'ed identifier per type */
63
64int	vflag;			/* verbose */
65int	aflag;			/* all file systems */
66
67void	usage(void);
68int	repquota(struct fstab *, int, char *);
69int	hasquota(struct fstab *, int, char **);
70int	oneof(char *, char *[], int);
71char	*timeprt(time_t);
72int
73main(int argc, char *argv[])
74{
75	struct fstab *fs;
76	struct passwd *pw;
77	struct group *gr;
78	int gflag = 0, uflag = 0, errs = 0;
79	long i, argnum, done = 0;
80	char *qfnp;
81	int ch;
82
83	while ((ch = getopt(argc, argv, "aguv")) != -1) {
84		switch(ch) {
85		case 'a':
86			aflag++;
87			break;
88		case 'g':
89			gflag++;
90			break;
91		case 'u':
92			uflag++;
93			break;
94		case 'v':
95			vflag++;
96			break;
97		default:
98			usage();
99		}
100	}
101	argc -= optind;
102	argv += optind;
103	if ((argc == 0) == (aflag == 0))
104		usage();
105	if (!gflag && !uflag) {
106		if (aflag)
107			gflag++;
108		uflag++;
109	}
110	if (gflag) {
111		setgrent();
112		while ((gr = getgrent()) != 0)
113			(void) addid((uid_t)gr->gr_gid, GRPQUOTA, gr->gr_name);
114		endgrent();
115	}
116	if (uflag) {
117		setpwent();
118		while ((pw = getpwent()) != 0)
119			(void) addid(pw->pw_uid, USRQUOTA, pw->pw_name);
120		endpwent();
121	}
122	setfsent();
123	while ((fs = getfsent()) != NULL) {
124		if (strcmp(fs->fs_vfstype, "ffs") &&
125		    strcmp(fs->fs_vfstype, "ufs") &&
126		    strcmp(fs->fs_vfstype, "mfs"))
127			continue;
128		if (aflag) {
129			if (gflag && hasquota(fs, GRPQUOTA, &qfnp))
130				errs += repquota(fs, GRPQUOTA, qfnp);
131			if (uflag && hasquota(fs, USRQUOTA, &qfnp))
132				errs += repquota(fs, USRQUOTA, qfnp);
133			continue;
134		}
135		if ((argnum = oneof(fs->fs_file, argv, argc)) >= 0 ||
136		    (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) {
137			done |= 1 << argnum;
138			if (gflag && hasquota(fs, GRPQUOTA, &qfnp))
139				errs += repquota(fs, GRPQUOTA, qfnp);
140			if (uflag && hasquota(fs, USRQUOTA, &qfnp))
141				errs += repquota(fs, USRQUOTA, qfnp);
142		}
143	}
144	endfsent();
145	for (i = 0; i < argc; i++)
146		if ((done & (1 << i)) == 0)
147			fprintf(stderr, "%s not found in fstab\n", argv[i]);
148	exit(errs);
149}
150
151void
152usage(void)
153{
154	extern char *__progname;
155	fprintf(stderr, "usage: %s [-aguv] filesystem ...\n", __progname);
156	exit(1);
157}
158
159int
160repquota(struct fstab *fs, int type, char *qfpathname)
161{
162	struct fileusage *fup;
163	FILE *qf;
164	uid_t id;
165	struct dqblk dqbuf;
166	char *timeprt(time_t);
167	static struct dqblk zerodqblk;
168	static int warned = 0;
169	static int multiple = 0;
170
171	if (quotactl(fs->fs_file, QCMD(Q_SYNC, type), 0, 0) < 0 &&
172	    errno == EOPNOTSUPP && !warned && vflag) {
173		warned++;
174		fprintf(stdout,
175		    "*** Warning: Quotas are not compiled into this kernel\n");
176	}
177	if (multiple++)
178		printf("\n");
179	if (vflag)
180		fprintf(stdout, "*** Report for %s quotas on %s (%s)\n",
181		    qfextension[type], fs->fs_file, fs->fs_spec);
182	if ((qf = fopen(qfpathname, "r")) == NULL) {
183		perror(qfpathname);
184		return (1);
185	}
186	for (id = 0; ; id++) {
187		fread(&dqbuf, sizeof(struct dqblk), 1, qf);
188		if (feof(qf))
189			break;
190		if (dqbuf.dqb_curinodes == 0 && dqbuf.dqb_curblocks == 0)
191			continue;
192		if ((fup = lookup(id, type)) == 0)
193			fup = addid(id, type, NULL);
194		fup->fu_dqblk = dqbuf;
195	}
196	fclose(qf);
197	printf("                        KByte limits               File limits\n");
198	printf("User            used    soft    hard  grace    used  soft  hard  grace\n");
199	for (id = 0; id <= highid[type]; id++) {
200		fup = lookup(id, type);
201		if (fup == 0)
202			continue;
203		if (fup->fu_dqblk.dqb_curinodes == 0 &&
204		    fup->fu_dqblk.dqb_curblocks == 0)
205			continue;
206		printf("%-10s", fup->fu_name);
207		printf("%c%c %7d %7d %7d %6s",
208			fup->fu_dqblk.dqb_bsoftlimit &&
209			    fup->fu_dqblk.dqb_curblocks >=
210			    fup->fu_dqblk.dqb_bsoftlimit ? '+' : '-',
211			fup->fu_dqblk.dqb_isoftlimit &&
212			    fup->fu_dqblk.dqb_curinodes >=
213			    fup->fu_dqblk.dqb_isoftlimit ? '+' : '-',
214			(int)(dbtob((u_quad_t)fup->fu_dqblk.dqb_curblocks)
215			    / 1024),
216			(int)(dbtob((u_quad_t)fup->fu_dqblk.dqb_bsoftlimit)
217			    / 1024),
218			(int)(dbtob((u_quad_t)fup->fu_dqblk.dqb_bhardlimit)
219			    / 1024),
220			fup->fu_dqblk.dqb_bsoftlimit &&
221			    fup->fu_dqblk.dqb_curblocks >=
222			    fup->fu_dqblk.dqb_bsoftlimit ?
223			    timeprt(fup->fu_dqblk.dqb_btime) : "");
224		printf("  %6d %5d %5d %6s\n",
225			fup->fu_dqblk.dqb_curinodes,
226			fup->fu_dqblk.dqb_isoftlimit,
227			fup->fu_dqblk.dqb_ihardlimit,
228			fup->fu_dqblk.dqb_isoftlimit &&
229			    fup->fu_dqblk.dqb_curinodes >=
230			    fup->fu_dqblk.dqb_isoftlimit ?
231			    timeprt(fup->fu_dqblk.dqb_itime) : "");
232		fup->fu_dqblk = zerodqblk;
233	}
234	return (0);
235}
236
237/*
238 * Check to see if target appears in list of size cnt.
239 */
240int
241oneof(char *target, char *list[], int cnt)
242{
243	int i;
244
245	for (i = 0; i < cnt; i++)
246		if (strcmp(target, list[i]) == 0)
247			return (i);
248	return (-1);
249}
250
251/*
252 * Check to see if a particular quota is to be enabled.
253 */
254int
255hasquota(struct fstab *fs, int type, char **qfnamep)
256{
257	char *opt;
258	char *cp;
259	static char initname, usrname[100], grpname[100];
260	static char buf[BUFSIZ];
261
262	if (!initname) {
263		(void)snprintf(usrname, sizeof usrname, "%s%s",
264		    qfextension[USRQUOTA], qfname);
265		(void)snprintf(grpname, sizeof grpname, "%s%s",
266		    qfextension[GRPQUOTA], qfname);
267		initname = 1;
268	}
269	strlcpy(buf, fs->fs_mntops, sizeof buf);
270	for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
271		if ((cp = strchr(opt, '=')))
272			*cp++ = '\0';
273		if (type == USRQUOTA && strcmp(opt, usrname) == 0)
274			break;
275		if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
276			break;
277	}
278	if (!opt)
279		return (0);
280	if (cp) {
281		*qfnamep = cp;
282		return (1);
283	}
284	(void)snprintf(buf, sizeof buf, "%s/%s.%s",
285	    fs->fs_file, qfname, qfextension[type]);
286	*qfnamep = buf;
287	return (1);
288}
289
290/*
291 * Routines to manage the file usage table.
292 *
293 * Lookup an id of a specific type.
294 */
295struct fileusage *
296lookup(uid_t id, int type)
297{
298	struct fileusage *fup;
299
300	for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next)
301		if (fup->fu_id == id)
302			return (fup);
303	return (NULL);
304}
305
306/*
307 * Add a new file usage id if it does not already exist.
308 */
309struct fileusage *
310addid(uid_t id, int type, char *name)
311{
312	struct fileusage *fup, **fhp;
313	size_t len;
314
315	if ((fup = lookup(id, type)))
316		return (fup);
317	if (name)
318		len = strlen(name);
319	else
320		len = 10;
321	if ((fup = calloc(1, sizeof(*fup) + len)) == NULL) {
322		fprintf(stderr, "out of memory for fileusage structures\n");
323		exit(1);
324	}
325	fhp = &fuhead[type][id & (FUHASH - 1)];
326	fup->fu_next = *fhp;
327	*fhp = fup;
328	fup->fu_id = id;
329	if (id > highid[type])
330		highid[type] = id;
331	if (name) {
332		bcopy(name, fup->fu_name, len + 1);
333	} else {
334		snprintf(fup->fu_name, len, "%u", id);
335	}
336	return (fup);
337}
338
339/*
340 * Calculate the grace period and return a printable string for it.
341 */
342char *
343timeprt(time_t seconds)
344{
345	int hours, minutes;
346	static char buf[20];
347	static time_t now;
348
349	if (now == 0)
350		time(&now);
351	if (now > seconds)
352		return ("none");
353	seconds -= now;
354	minutes = (seconds + 30) / 60;
355	hours = (minutes + 30) / 60;
356	if (hours >= 36) {
357		snprintf(buf, sizeof buf, "%ddays", (hours + 12) / 24);
358		return (buf);
359	}
360	if (minutes >= 60) {
361		snprintf(buf, sizeof buf, "%2d:%d", minutes / 60,
362		    minutes % 60);
363		return (buf);
364	}
365	snprintf(buf, sizeof buf, "%2d", minutes);
366	return (buf);
367}
368