xref: /netbsd-src/usr.sbin/repquota/repquota.c (revision 8ee626c9fa19b110a740cae0b27b2678014c2a70)
1 /*	$NetBSD: repquota.c,v 1.45 2015/06/16 23:04:14 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1980, 1990, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Robert Elz at The University of Melbourne.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 #ifndef lint
37 __COPYRIGHT("@(#) Copyright (c) 1980, 1990, 1993\
38  The Regents of the University of California.  All rights reserved.");
39 #endif /* not lint */
40 
41 #ifndef lint
42 #if 0
43 static char sccsid[] = "@(#)repquota.c	8.2 (Berkeley) 11/22/94";
44 #else
45 __RCSID("$NetBSD: repquota.c,v 1.45 2015/06/16 23:04:14 christos Exp $");
46 #endif
47 #endif /* not lint */
48 
49 /*
50  * Quota report
51  */
52 #include <sys/param.h>
53 #include <sys/stat.h>
54 #include <sys/types.h>
55 #include <sys/statvfs.h>
56 
57 #include <errno.h>
58 #include <err.h>
59 #include <fstab.h>
60 #include <grp.h>
61 #include <pwd.h>
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <string.h>
65 #include <unistd.h>
66 
67 #include <quota.h>
68 
69 #include "printquota.h"
70 
71 /*
72  * XXX. Ideally we shouldn't compile either of these in, but it's a
73  * nontrivial rework to avoid it and it'll work ok for now.
74  */
75 #define REPQUOTA_NUMIDTYPES	2
76 #define REPQUOTA_NUMOBJTYPES	2
77 
78 struct fileusage {
79 	struct	fileusage *fu_next;
80 	struct	quotaval fu_qv[REPQUOTA_NUMOBJTYPES];
81 	uint32_t	fu_id;
82 	char	fu_name[1];
83 	/* actually bigger */
84 };
85 
86 #define FUHASH 1024	/* must be power of two */
87 static struct fileusage *fuhead[REPQUOTA_NUMIDTYPES][FUHASH];
88 
89 /* highest addid()'ed identifier per idtype */
90 static uint32_t highid[REPQUOTA_NUMIDTYPES];
91 
92 int valid[REPQUOTA_NUMIDTYPES];
93 
94 static struct quotaval defaultqv[REPQUOTA_NUMIDTYPES][REPQUOTA_NUMOBJTYPES];
95 
96 static int	vflag = 0;		/* verbose */
97 static int	aflag = 0;		/* all file systems */
98 static int	hflag = 0;		/* humanize */
99 static int	xflag = 0;		/* export */
100 
101 /*
102  * XXX this should go away and be replaced with a call to
103  * quota_idtype_getname(), but that needs a quotahandle and requires
104  * the same nontrivial rework as getting rid of REPQUOTA_NUMIDTYPES.
105  */
106 static const char *const repquota_idtype_names[REPQUOTA_NUMIDTYPES] = {
107 	"user",
108 	"group",
109 };
110 
111 static struct fileusage *addid(uint32_t, int, const char *);
112 static struct fileusage *lookup(uint32_t, int);
113 static struct fileusage *qremove(uint32_t, int);
114 static int	repquota(struct quotahandle *, int);
115 static void	usage(void) __attribute__((__noreturn__));
116 static void	printquotas(int, struct quotahandle *);
117 static void	exportquotas(void);
118 static int	oneof(const char *, char *[], int cnt);
119 static int isover(struct quotaval *qv, time_t now);
120 
121 int
main(int argc,char ** argv)122 main(int argc, char **argv)
123 {
124 	int gflag = 0, uflag = 0, errs = 0;
125 	long i, argnum, done = 0;
126 	int ch;
127 	struct statvfs *fst;
128 	int nfst;
129 	struct quotahandle *qh;
130 
131 	if (!strcmp(getprogname(), "quotadump")) {
132 		xflag = 1;
133 	}
134 
135 	while ((ch = getopt(argc, argv, "aguhvx")) != -1) {
136 		switch(ch) {
137 		case 'a':
138 			aflag++;
139 			break;
140 		case 'g':
141 			gflag++;
142 			break;
143 		case 'u':
144 			uflag++;
145 			break;
146 		case 'h':
147 			hflag++;
148 			break;
149 		case 'v':
150 			vflag++;
151 			break;
152 		case 'x':
153 			xflag++;
154 			break;
155 		default:
156 			usage();
157 		}
158 	}
159 	argc -= optind;
160 	argv += optind;
161 	if (xflag && (argc != 1 || aflag))
162 		usage();
163 	if (argc == 0 && !aflag)
164 		usage();
165 	if (!gflag && !uflag) {
166 		if (aflag)
167 			gflag++;
168 		uflag++;
169 	}
170 
171 	nfst = getmntinfo(&fst, MNT_WAIT);
172 	if (nfst == 0)
173 		errx(1, "no filesystems mounted!");
174 	for (i = 0; i < nfst; i++) {
175 		if ((fst[i].f_flag & ST_QUOTA) == 0)
176 			continue;
177 		/* check if we want this volume */
178 		if (!aflag) {
179 			argnum = oneof(fst[i].f_mntonname, argv, argc);
180 			if (argnum < 0) {
181 				argnum = oneof(fst[i].f_mntfromname,
182 					       argv, argc);
183 			}
184 			if (argnum < 0) {
185 				continue;
186 			}
187 			done |= 1U << argnum;
188 		}
189 
190 		qh = quota_open(fst[i].f_mntonname);
191 		if (qh == NULL) {
192 			/* XXX: check this errno */
193 			if (errno == EOPNOTSUPP || errno == ENXIO) {
194 				continue;
195 			}
196 			warn("%s: quota_open", fst[i].f_mntonname);
197 			continue;
198 		}
199 
200 		if (gflag)
201 			errs += repquota(qh, QUOTA_IDTYPE_GROUP);
202 		if (uflag)
203 			errs += repquota(qh, QUOTA_IDTYPE_USER);
204 
205 		quota_close(qh);
206 	}
207 	if (xflag)
208 		exportquotas();
209 	for (i = 0; i < argc; i++)
210 		if ((done & (1U << i)) == 0)
211 			warnx("%s not mounted", argv[i]);
212 	return errs;
213 }
214 
215 static void
usage(void)216 usage(void)
217 {
218 	const char *p = getprogname();
219 	fprintf(stderr, "usage: %s [-D] [-v] [-g] [-u] -a\n"
220 		"\t%s [-D] [-v] [-g] [-u] filesys ...\n"
221 		"\t%s -x [-D] [-g] [-u] filesys\n", p, p, p);
222 	exit(1);
223 }
224 
225 static int
repquota(struct quotahandle * qh,int idtype)226 repquota(struct quotahandle *qh, int idtype)
227 {
228 	struct quotacursor *qc;
229 	struct quotakey qk;
230 	struct quotaval qv;
231 	struct quotaval *qvp;
232 	struct fileusage *fup;
233 
234 	qc = quota_opencursor(qh);
235 	if (qc == NULL) {
236 		return 1;
237 	}
238 
239 	if (idtype == QUOTA_IDTYPE_USER) {
240 		quotacursor_skipidtype(qc, QUOTA_IDTYPE_GROUP);
241 	}
242 	if (idtype == QUOTA_IDTYPE_GROUP) {
243 		quotacursor_skipidtype(qc, QUOTA_IDTYPE_USER);
244 	}
245 
246 	valid[idtype] = 0;
247 	while (!quotacursor_atend(qc)) {
248 		if (quotacursor_get(qc, &qk, &qv)) {
249 			err(1, "%s: quotacursor_get", quota_getmountpoint(qh));
250 		}
251 		if (qk.qk_idtype != idtype) {
252 			continue;
253 		}
254 
255 		valid[idtype] = 1;
256 		if (qk.qk_id == QUOTA_DEFAULTID) {
257 			qvp = defaultqv[idtype];
258 		} else {
259 			if ((fup = lookup(qk.qk_id, idtype)) == 0)
260 				fup = addid(qk.qk_id, idtype, (char *)0);
261 			qvp = fup->fu_qv;
262 		}
263 		if (qk.qk_objtype == QUOTA_OBJTYPE_BLOCKS) {
264 			qvp[QUOTA_OBJTYPE_BLOCKS] = qv;
265 		} else if (qk.qk_objtype == QUOTA_OBJTYPE_FILES) {
266 			qvp[QUOTA_OBJTYPE_FILES] = qv;
267 		}
268 	}
269 
270 	if (xflag == 0 && valid[idtype])
271 		printquotas(idtype, qh);
272 
273 	return 0;
274 }
275 
276 static void
printquotas(int idtype,struct quotahandle * qh)277 printquotas(int idtype, struct quotahandle *qh)
278 {
279 	static int multiple = 0;
280 	uint32_t id;
281 	int i;
282 	struct fileusage *fup;
283 	struct quotaval *q;
284 	const char *timemsg[REPQUOTA_NUMOBJTYPES];
285 	char overchar[REPQUOTA_NUMOBJTYPES];
286 	time_t now;
287 	char b0[2][20], b1[20], b2[20], b3[20];
288 	int ok, objtype;
289 	int isbytes, width;
290 
291 	switch (idtype) {
292 	case QUOTA_IDTYPE_GROUP:
293 		{
294 		struct group *gr;
295 		setgrent();
296 		while ((gr = getgrent()) != 0)
297 			(void)addid(gr->gr_gid, idtype, gr->gr_name);
298 		endgrent();
299 		break;
300 		}
301 	case QUOTA_IDTYPE_USER:
302 		{
303 		struct passwd *pw;
304 		setpwent();
305 		while ((pw = getpwent()) != 0)
306 			(void)addid(pw->pw_uid, idtype, pw->pw_name);
307 		endpwent();
308 		break;
309 		}
310 	default:
311 		errx(1, "Unknown quota ID type %d", idtype);
312 	}
313 
314 	time(&now);
315 
316 	if (multiple++)
317 		printf("\n");
318 	if (vflag)
319 		printf("*** Report for %s quotas on %s (%s: %s)\n",
320 		    repquota_idtype_names[idtype], quota_getmountpoint(qh),
321 		    quota_getmountdevice(qh), quota_getimplname(qh));
322 	printf("                        Block limits               "
323 	    "File limits\n");
324 	printf(idtype == QUOTA_IDTYPE_USER ? "User " : "Group");
325 	printf("            used     soft     hard  grace      used"
326 	    "    soft    hard  grace\n");
327 	for (id = 0; id <= highid[idtype]; id++) {
328 		fup = qremove(id, idtype);
329 		q = fup->fu_qv;
330 		if (fup == 0)
331 			continue;
332 		for (i = 0; i < REPQUOTA_NUMOBJTYPES; i++) {
333 			if (isover(&q[i], now)) {
334 				timemsg[i] = timeprt(b0[i], 8, now,
335 				    q[i].qv_expiretime);
336 				overchar[i] = '+';
337 			} else {
338 				if (vflag && q[i].qv_grace != QUOTA_NOTIME) {
339 					timemsg[i] = timeprt(b0[i], 8, 0,
340 							     q[i].qv_grace);
341 				} else {
342 					timemsg[i] = "";
343 				}
344 				overchar[i] = '-';
345 			}
346 		}
347 
348 		ok = 1;
349 		for (objtype = 0; objtype < REPQUOTA_NUMOBJTYPES; objtype++) {
350 			if (q[objtype].qv_usage != 0 ||
351 			    overchar[objtype] != '-') {
352 				ok = 0;
353 			}
354 		}
355 		if (ok && vflag == 0)
356 			continue;
357 		if (strlen(fup->fu_name) > 9)
358 			printf("%s ", fup->fu_name);
359 		else
360 			printf("%-10s", fup->fu_name);
361 		for (objtype = 0; objtype < REPQUOTA_NUMOBJTYPES; objtype++) {
362 			printf("%c", overchar[objtype]);
363 		}
364 		for (objtype = 0; objtype < REPQUOTA_NUMOBJTYPES; objtype++) {
365 			isbytes = quota_objtype_isbytes(qh, objtype);
366 			width = isbytes ? 9 : 8;
367 			printf("%*s%*s%*s%7s",
368 			       width,
369 			       intprt(b1, width+1, q[objtype].qv_usage,
370 				      isbytes ? HN_B : 0, hflag),
371 			       width,
372 			       intprt(b2, width+1, q[objtype].qv_softlimit,
373 				      isbytes ? HN_B : 0, hflag),
374 			       width,
375 			       intprt(b3, width+1, q[objtype].qv_hardlimit,
376 				      isbytes ? HN_B : 0, hflag),
377 			       timemsg[objtype]);
378 
379 			if (objtype + 1 < REPQUOTA_NUMOBJTYPES) {
380 				printf("  ");
381 			} else {
382 				printf("\n");
383 			}
384 		}
385 		free(fup);
386 	}
387 }
388 
389 static void
exportquotaval(const struct quotaval * qv)390 exportquotaval(const struct quotaval *qv)
391 {
392 	if (qv->qv_hardlimit == QUOTA_NOLIMIT) {
393 		printf(" -");
394 	} else {
395 		printf(" %llu", (unsigned long long)qv->qv_hardlimit);
396 	}
397 
398 	if (qv->qv_softlimit == QUOTA_NOLIMIT) {
399 		printf(" -");
400 	} else {
401 		printf(" %llu", (unsigned long long)qv->qv_softlimit);
402 	}
403 
404 	printf(" %llu", (unsigned long long)qv->qv_usage);
405 
406 	if (qv->qv_expiretime == QUOTA_NOTIME) {
407 		printf(" -");
408 	} else {
409 		printf(" %lld", (long long)qv->qv_expiretime);
410 	}
411 
412 	if (qv->qv_grace == QUOTA_NOTIME) {
413 		printf(" -");
414 	} else {
415 		printf(" %lld", (long long)qv->qv_grace);
416 	}
417 }
418 
419 static void
exportquotas(void)420 exportquotas(void)
421 {
422 	int idtype;
423 	id_t id;
424 	struct fileusage *fup;
425 
426 	/* header */
427 	printf("@format netbsd-quota-dump v1\n");
428 	printf("# idtype id objtype   hard soft usage expire grace\n");
429 
430 	for (idtype = 0; idtype < REPQUOTA_NUMIDTYPES; idtype++) {
431 		if (valid[idtype] == 0)
432 			continue;
433 
434 		printf("%s default block  ", repquota_idtype_names[idtype]);
435 		exportquotaval(&defaultqv[idtype][QUOTA_OBJTYPE_BLOCKS]);
436 		printf("\n");
437 
438 		printf("%s default file  ", repquota_idtype_names[idtype]);
439 		exportquotaval(&defaultqv[idtype][QUOTA_OBJTYPE_FILES]);
440 		printf("\n");
441 
442 		for (id = 0; id <= highid[idtype]; id++) {
443 			fup = qremove(id, idtype);
444 			if (fup == 0)
445 				continue;
446 
447 			printf("%s %u block  ", repquota_idtype_names[idtype],
448 			       id);
449 			exportquotaval(&fup->fu_qv[QUOTA_OBJTYPE_BLOCKS]);
450 			printf("\n");
451 
452 			printf("%s %u file  ", repquota_idtype_names[idtype],
453 			       id);
454 			exportquotaval(&fup->fu_qv[QUOTA_OBJTYPE_FILES]);
455 			printf("\n");
456 
457 			free(fup);
458 		}
459 	}
460 	printf("@end\n");
461 }
462 
463 /*
464  * Routines to manage the file usage table.
465  *
466  * Lookup an id of a specific id type.
467  */
468 struct fileusage *
lookup(uint32_t id,int idtype)469 lookup(uint32_t id, int idtype)
470 {
471 	struct fileusage *fup;
472 
473 	for (fup = fuhead[idtype][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next)
474 		if (fup->fu_id == id)
475 			return fup;
476 	return NULL;
477 }
478 /*
479  * Lookup and remove an id of a specific id type.
480  */
481 static struct fileusage *
qremove(uint32_t id,int idtype)482 qremove(uint32_t id, int idtype)
483 {
484 	struct fileusage *fup, **fupp;
485 
486 	for (fupp = &fuhead[idtype][id & (FUHASH-1)]; *fupp != 0;) {
487 		fup = *fupp;
488 		if (fup->fu_id == id) {
489 			*fupp = fup->fu_next;
490 			return fup;
491 		}
492 		fupp = &fup->fu_next;
493 	}
494 	return NULL;
495 }
496 
497 /*
498  * Add a new file usage id if it does not already exist.
499  */
500 static struct fileusage *
addid(uint32_t id,int idtype,const char * name)501 addid(uint32_t id, int idtype, const char *name)
502 {
503 	struct fileusage *fup, **fhp;
504 	struct group *gr = NULL;
505 	struct passwd *pw = NULL;
506 	size_t len;
507 
508 	if ((fup = lookup(id, idtype)) != NULL) {
509 		return fup;
510 	}
511 	if (name == NULL) {
512 		switch(idtype) {
513 		case  QUOTA_IDTYPE_GROUP:
514 			gr = getgrgid(id);
515 
516 			if (gr != NULL)
517 				name = gr->gr_name;
518 			break;
519 		case QUOTA_IDTYPE_USER:
520 			pw = getpwuid(id);
521 			if (pw)
522 				name = pw->pw_name;
523 			break;
524 		default:
525 			errx(1, "Unknown quota ID type %d", idtype);
526 		}
527 	}
528 
529 	if (name)
530 		len = strlen(name);
531 	else
532 		len = 10;
533 	if ((fup = calloc(1, sizeof(*fup) + len)) == NULL)
534 		err(1, "out of memory for fileusage structures");
535 	fhp = &fuhead[idtype][id & (FUHASH - 1)];
536 	fup->fu_next = *fhp;
537 	*fhp = fup;
538 	fup->fu_id = id;
539 	if (id > highid[idtype])
540 		highid[idtype] = id;
541 	if (name) {
542 		memmove(fup->fu_name, name, len + 1);
543 	} else {
544 		snprintf(fup->fu_name, len + 1, "%u", id);
545 	}
546 	/*
547 	 * XXX nothing guarantees the default limits have been loaded yet
548 	 */
549 	fup->fu_qv[QUOTA_OBJTYPE_BLOCKS] = defaultqv[idtype][QUOTA_OBJTYPE_BLOCKS];
550 	fup->fu_qv[QUOTA_OBJTYPE_FILES] = defaultqv[idtype][QUOTA_OBJTYPE_FILES];
551 	return fup;
552 }
553 
554 /*
555  * Check to see if target appears in list of size cnt.
556  */
557 static int
oneof(const char * target,char * list[],int cnt)558 oneof(const char *target, char *list[], int cnt)
559 {
560 	int i;
561 
562 	for (i = 0; i < cnt; i++)
563 		if (strcmp(target, list[i]) == 0)
564 			return i;
565 	return -1;
566 }
567 
568 static int
isover(struct quotaval * qv,time_t now)569 isover(struct quotaval *qv, time_t now)
570 {
571 	return (qv->qv_usage >= qv->qv_hardlimit ||
572 		qv->qv_usage >= qv->qv_softlimit);
573 }
574