xref: /original-bsd/sbin/quotacheck/quotacheck.c (revision 95ecee29)
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  * %sccs.include.redist.c%
9  */
10 
11 #ifndef lint
12 static char copyright[] =
13 "@(#) Copyright (c) 1980, 1990, 1993\n\
14 	The Regents of the University of California.  All rights reserved.\n";
15 #endif /* not lint */
16 
17 #ifndef lint
18 static char sccsid[] = "@(#)quotacheck.c	8.2 (Berkeley) 09/23/93";
19 #endif /* not lint */
20 
21 /*
22  * Fix up / report on disk quotas & usage
23  */
24 #include <sys/param.h>
25 #include <sys/stat.h>
26 
27 #include <ufs/ufs/dinode.h>
28 #include <ufs/ufs/quota.h>
29 #include <ufs/ffs/fs.h>
30 
31 #include <fcntl.h>
32 #include <fstab.h>
33 #include <pwd.h>
34 #include <grp.h>
35 #include <errno.h>
36 #include <unistd.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 
41 char *qfname = QUOTAFILENAME;
42 char *qfextension[] = INITQFNAMES;
43 char *quotagroup = QUOTAGROUP;
44 
45 union {
46 	struct	fs	sblk;
47 	char	dummy[MAXBSIZE];
48 } un;
49 #define	sblock	un.sblk
50 long dev_bsize = 1;
51 long maxino;
52 
53 struct quotaname {
54 	long	flags;
55 	char	grpqfname[MAXPATHLEN + 1];
56 	char	usrqfname[MAXPATHLEN + 1];
57 };
58 #define	HASUSR	1
59 #define	HASGRP	2
60 
61 struct fileusage {
62 	struct	fileusage *fu_next;
63 	u_long	fu_curinodes;
64 	u_long	fu_curblocks;
65 	u_long	fu_id;
66 	char	fu_name[1];
67 	/* actually bigger */
68 };
69 #define FUHASH 1024	/* must be power of two */
70 struct fileusage *fuhead[MAXQUOTAS][FUHASH];
71 
72 int	aflag;			/* all file systems */
73 int	gflag;			/* check group quotas */
74 int	uflag;			/* check user quotas */
75 int	vflag;			/* verbose */
76 int	fi;			/* open disk file descriptor */
77 u_long	highid[MAXQUOTAS];	/* highest addid()'ed identifier per type */
78 
79 struct fileusage *
80 	 addid __P((u_long, int, char *));
81 char	*blockcheck __P((char *));
82 void	 bread __P((daddr_t, char *, long));
83 int	 chkquota __P((char *, char *, struct quotaname *));
84 void	 err __P((const char *, ...));
85 void	 freeinodebuf __P((void));
86 struct dinode *
87 	 getnextinode __P((ino_t));
88 int	 getquotagid __P((void));
89 int	 hasquota __P((struct fstab *, int, char **));
90 struct fileusage *
91 	 lookup __P((u_long, int));
92 void	*needchk __P((struct fstab *));
93 int	 oneof __P((char *, char*[], int));
94 void	 resetinodebuf __P((void));
95 int	 update __P((char *, char *, int));
96 void	 usage __P((void));
97 
98 int
99 main(argc, argv)
100 	int argc;
101 	char *argv[];
102 {
103 	register struct fstab *fs;
104 	register struct passwd *pw;
105 	register struct group *gr;
106 	struct quotaname *auxdata;
107 	int i, argnum, maxrun, errs = 0;
108 	long done = 0;
109 	char ch, *name;
110 
111 	while ((ch = getopt(argc, argv, "aguvl:")) != EOF) {
112 		switch(ch) {
113 		case 'a':
114 			aflag++;
115 			break;
116 		case 'g':
117 			gflag++;
118 			break;
119 		case 'u':
120 			uflag++;
121 			break;
122 		case 'v':
123 			vflag++;
124 			break;
125 		case 'l':
126 			maxrun = atoi(optarg);
127 			break;
128 		default:
129 			usage();
130 		}
131 	}
132 	argc -= optind;
133 	argv += optind;
134 	if ((argc == 0 && !aflag) || (argc > 0 && aflag))
135 		usage();
136 	if (!gflag && !uflag) {
137 		gflag++;
138 		uflag++;
139 	}
140 	if (gflag) {
141 		setgrent();
142 		while ((gr = getgrent()) != 0)
143 			(void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name);
144 		endgrent();
145 	}
146 	if (uflag) {
147 		setpwent();
148 		while ((pw = getpwent()) != 0)
149 			(void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name);
150 		endpwent();
151 	}
152 	if (aflag)
153 		exit(checkfstab(1, maxrun, needchk, chkquota));
154 	if (setfsent() == 0)
155 		err("%s: can't open", FSTAB);
156 	while ((fs = getfsent()) != NULL) {
157 		if (((argnum = oneof(fs->fs_file, argv, argc)) >= 0 ||
158 		    (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) &&
159 		    (auxdata = needchk(fs)) &&
160 		    (name = blockcheck(fs->fs_spec))) {
161 			done |= 1 << argnum;
162 			errs += chkquota(name, fs->fs_file, auxdata);
163 		}
164 	}
165 	endfsent();
166 	for (i = 0; i < argc; i++)
167 		if ((done & (1 << i)) == 0)
168 			fprintf(stderr, "%s not found in %s\n",
169 				argv[i], FSTAB);
170 	exit(errs);
171 }
172 
173 void
174 usage()
175 {
176 	(void)fprintf(stderr, "usage:\t%s\n\t%s\n",
177 		"quotacheck -a [-guv]",
178 		"quotacheck [-guv] filesys ...");
179 	exit(1);
180 }
181 
182 void *
183 needchk(fs)
184 	register struct fstab *fs;
185 {
186 	register struct quotaname *qnp;
187 	char *qfnp;
188 
189 	if (strcmp(fs->fs_vfstype, "ufs") ||
190 	    strcmp(fs->fs_type, FSTAB_RW))
191 		return (NULL);
192 	if ((qnp = malloc(sizeof(*qnp))) == NULL)
193 		err("%s", strerror(errno));
194 	qnp->flags = 0;
195 	if (gflag && hasquota(fs, GRPQUOTA, &qfnp)) {
196 		strcpy(qnp->grpqfname, qfnp);
197 		qnp->flags |= HASGRP;
198 	}
199 	if (uflag && hasquota(fs, USRQUOTA, &qfnp)) {
200 		strcpy(qnp->usrqfname, qfnp);
201 		qnp->flags |= HASUSR;
202 	}
203 	if (qnp->flags)
204 		return (qnp);
205 	free(qnp);
206 	return (NULL);
207 }
208 
209 /*
210  * Scan the specified filesystem to check quota(s) present on it.
211  */
212 int
213 chkquota(fsname, mntpt, qnp)
214 	char *fsname, *mntpt;
215 	register struct quotaname *qnp;
216 {
217 	register struct fileusage *fup;
218 	register struct dinode *dp;
219 	int cg, i, mode, errs = 0;
220 	ino_t ino;
221 
222 	if ((fi = open(fsname, O_RDONLY, 0)) < 0) {
223 		perror(fsname);
224 		return (1);
225 	}
226 	if (vflag) {
227 		(void)printf("*** Checking ");
228 		if (qnp->flags & HASUSR)
229 			(void)printf("%s%s", qfextension[USRQUOTA],
230 			    (qnp->flags & HASGRP) ? " and " : "");
231 		if (qnp->flags & HASGRP)
232 			(void)printf("%s", qfextension[GRPQUOTA]);
233 		(void)printf(" quotas for %s (%s)\n", fsname, mntpt);
234 	}
235 	sync();
236 	bread(SBOFF, (char *)&sblock, (long)SBSIZE);
237 	dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1);
238 	maxino = sblock.fs_ncg * sblock.fs_ipg;
239 	resetinodebuf();
240 	for (ino = 0, cg = 0; cg < sblock.fs_ncg; cg++) {
241 		for (i = 0; i < sblock.fs_ipg; i++, ino++) {
242 			if (ino < ROOTINO)
243 				continue;
244 			if ((dp = getnextinode(ino)) == NULL)
245 				continue;
246 			if ((mode = dp->di_mode & IFMT) == 0)
247 				continue;
248 			if (qnp->flags & HASGRP) {
249 				fup = addid((u_long)dp->di_gid, GRPQUOTA,
250 				    (char *)0);
251 				fup->fu_curinodes++;
252 				if (mode == IFREG || mode == IFDIR ||
253 				    mode == IFLNK)
254 					fup->fu_curblocks += dp->di_blocks;
255 			}
256 			if (qnp->flags & HASUSR) {
257 				fup = addid((u_long)dp->di_uid, USRQUOTA,
258 				    (char *)0);
259 				fup->fu_curinodes++;
260 				if (mode == IFREG || mode == IFDIR ||
261 				    mode == IFLNK)
262 					fup->fu_curblocks += dp->di_blocks;
263 			}
264 		}
265 	}
266 	freeinodebuf();
267 	if (qnp->flags & HASUSR)
268 		errs += update(mntpt, qnp->usrqfname, USRQUOTA);
269 	if (qnp->flags & HASGRP)
270 		errs += update(mntpt, qnp->grpqfname, GRPQUOTA);
271 	close(fi);
272 	return (errs);
273 }
274 
275 /*
276  * Update a specified quota file.
277  */
278 int
279 update(fsname, quotafile, type)
280 	char *fsname, *quotafile;
281 	register int type;
282 {
283 	register struct fileusage *fup;
284 	register FILE *qfi, *qfo;
285 	register u_long id, lastid;
286 	struct dqblk dqbuf;
287 	static int warned = 0;
288 	static struct dqblk zerodqbuf;
289 	static struct fileusage zerofileusage;
290 
291 	if ((qfo = fopen(quotafile, "r+")) == NULL) {
292 		if (errno == ENOENT)
293 			qfo = fopen(quotafile, "w+");
294 		if (qfo) {
295 			(void) fprintf(stderr,
296 			    "quotacheck: creating quota file %s\n", quotafile);
297 #define	MODE	(S_IRUSR|S_IWUSR|S_IRGRP)
298 			(void) fchown(fileno(qfo), getuid(), getquotagid());
299 			(void) fchmod(fileno(qfo), MODE);
300 		} else {
301 			(void) fprintf(stderr,
302 			    "quotacheck: %s: %s\n", quotafile, strerror(errno));
303 			return (1);
304 		}
305 	}
306 	if ((qfi = fopen(quotafile, "r")) == NULL) {
307 		(void) fprintf(stderr,
308 		    "quotacheck: %s: %s\n", quotafile, strerror(errno));
309 		(void) fclose(qfo);
310 		return (1);
311 	}
312 	if (quotactl(fsname, QCMD(Q_SYNC, type), (u_long)0, (caddr_t)0) < 0 &&
313 	    errno == EOPNOTSUPP && !warned && vflag) {
314 		warned++;
315 		(void)printf("*** Warning: %s\n",
316 		    "Quotas are not compiled into this kernel");
317 	}
318 	for (lastid = highid[type], id = 0; id <= lastid; id++) {
319 		if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0)
320 			dqbuf = zerodqbuf;
321 		if ((fup = lookup(id, type)) == 0)
322 			fup = &zerofileusage;
323 		if (dqbuf.dqb_curinodes == fup->fu_curinodes &&
324 		    dqbuf.dqb_curblocks == fup->fu_curblocks) {
325 			fup->fu_curinodes = 0;
326 			fup->fu_curblocks = 0;
327 			fseek(qfo, (long)sizeof(struct dqblk), 1);
328 			continue;
329 		}
330 		if (vflag) {
331 			if (aflag)
332 				printf("%s: ", fsname);
333 			printf("%-8s fixed:", fup->fu_name);
334 			if (dqbuf.dqb_curinodes != fup->fu_curinodes)
335 				(void)printf("\tinodes %d -> %d",
336 					dqbuf.dqb_curinodes, fup->fu_curinodes);
337 			if (dqbuf.dqb_curblocks != fup->fu_curblocks)
338 				(void)printf("\tblocks %d -> %d",
339 					dqbuf.dqb_curblocks, fup->fu_curblocks);
340 			(void)printf("\n");
341 		}
342 		/*
343 		 * Reset time limit if have a soft limit and were
344 		 * previously under it, but are now over it.
345 		 */
346 		if (dqbuf.dqb_bsoftlimit &&
347 		    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
348 		    fup->fu_curblocks >= dqbuf.dqb_bsoftlimit)
349 			dqbuf.dqb_btime = 0;
350 		if (dqbuf.dqb_isoftlimit &&
351 		    dqbuf.dqb_curblocks < dqbuf.dqb_isoftlimit &&
352 		    fup->fu_curblocks >= dqbuf.dqb_isoftlimit)
353 			dqbuf.dqb_itime = 0;
354 		dqbuf.dqb_curinodes = fup->fu_curinodes;
355 		dqbuf.dqb_curblocks = fup->fu_curblocks;
356 		fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo);
357 		(void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
358 		    (caddr_t)&dqbuf);
359 		fup->fu_curinodes = 0;
360 		fup->fu_curblocks = 0;
361 	}
362 	fclose(qfi);
363 	fflush(qfo);
364 	ftruncate(fileno(qfo),
365 	    (off_t)((highid[type] + 1) * sizeof(struct dqblk)));
366 	fclose(qfo);
367 	return (0);
368 }
369 
370 /*
371  * Check to see if target appears in list of size cnt.
372  */
373 int
374 oneof(target, list, cnt)
375 	register char *target, *list[];
376 	int cnt;
377 {
378 	register int i;
379 
380 	for (i = 0; i < cnt; i++)
381 		if (strcmp(target, list[i]) == 0)
382 			return (i);
383 	return (-1);
384 }
385 
386 /*
387  * Determine the group identifier for quota files.
388  */
389 int
390 getquotagid()
391 {
392 	struct group *gr;
393 
394 	if (gr = getgrnam(quotagroup))
395 		return (gr->gr_gid);
396 	return (-1);
397 }
398 
399 /*
400  * Check to see if a particular quota is to be enabled.
401  */
402 int
403 hasquota(fs, type, qfnamep)
404 	register struct fstab *fs;
405 	int type;
406 	char **qfnamep;
407 {
408 	register char *opt;
409 	char *cp;
410 	static char initname, usrname[100], grpname[100];
411 	static char buf[BUFSIZ];
412 
413 	if (!initname) {
414 		(void)snprintf(usrname, sizeof(usrname),
415 		    "%s%s", qfextension[USRQUOTA], qfname);
416 		(void)snprintf(grpname, sizeof(grpname),
417 		    "%s%s", qfextension[GRPQUOTA], qfname);
418 		initname = 1;
419 	}
420 	strcpy(buf, fs->fs_mntops);
421 	for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
422 		if (cp = index(opt, '='))
423 			*cp++ = '\0';
424 		if (type == USRQUOTA && strcmp(opt, usrname) == 0)
425 			break;
426 		if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
427 			break;
428 	}
429 	if (!opt)
430 		return (0);
431 	if (cp)
432 		*qfnamep = cp;
433 	else {
434 		(void)snprintf(buf, sizeof(buf),
435 		    "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
436 		*qfnamep = buf;
437 	}
438 	return (1);
439 }
440 
441 /*
442  * Routines to manage the file usage table.
443  *
444  * Lookup an id of a specific type.
445  */
446 struct fileusage *
447 lookup(id, type)
448 	u_long id;
449 	int type;
450 {
451 	register struct fileusage *fup;
452 
453 	for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next)
454 		if (fup->fu_id == id)
455 			return (fup);
456 	return (NULL);
457 }
458 
459 /*
460  * Add a new file usage id if it does not already exist.
461  */
462 struct fileusage *
463 addid(id, type, name)
464 	u_long id;
465 	int type;
466 	char *name;
467 {
468 	struct fileusage *fup, **fhp;
469 	int len;
470 
471 	if (fup = lookup(id, type))
472 		return (fup);
473 	if (name)
474 		len = strlen(name);
475 	else
476 		len = 10;
477 	if ((fup = calloc(1, sizeof(*fup) + len)) == NULL)
478 		err("%s", strerror(errno));
479 	fhp = &fuhead[type][id & (FUHASH - 1)];
480 	fup->fu_next = *fhp;
481 	*fhp = fup;
482 	fup->fu_id = id;
483 	if (id > highid[type])
484 		highid[type] = id;
485 	if (name)
486 		bcopy(name, fup->fu_name, len + 1);
487 	else
488 		(void)sprintf(fup->fu_name, "%u", id);
489 	return (fup);
490 }
491 
492 /*
493  * Special purpose version of ginode used to optimize pass
494  * over all the inodes in numerical order.
495  */
496 ino_t nextino, lastinum;
497 long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize;
498 struct dinode *inodebuf;
499 #define	INOBUFSIZE	56*1024	/* size of buffer to read inodes */
500 
501 struct dinode *
502 getnextinode(inumber)
503 	ino_t inumber;
504 {
505 	long size;
506 	daddr_t dblk;
507 	static struct dinode *dp;
508 
509 	if (inumber != nextino++ || inumber > maxino)
510 		err("bad inode number %d to nextinode", inumber);
511 	if (inumber >= lastinum) {
512 		readcnt++;
513 		dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum));
514 		if (readcnt % readpercg == 0) {
515 			size = partialsize;
516 			lastinum += partialcnt;
517 		} else {
518 			size = inobufsize;
519 			lastinum += fullcnt;
520 		}
521 		bread(dblk, (char *)inodebuf, size);
522 		dp = inodebuf;
523 	}
524 	return (dp++);
525 }
526 
527 /*
528  * Prepare to scan a set of inodes.
529  */
530 void
531 resetinodebuf()
532 {
533 
534 	nextino = 0;
535 	lastinum = 0;
536 	readcnt = 0;
537 	inobufsize = blkroundup(&sblock, INOBUFSIZE);
538 	fullcnt = inobufsize / sizeof(struct dinode);
539 	readpercg = sblock.fs_ipg / fullcnt;
540 	partialcnt = sblock.fs_ipg % fullcnt;
541 	partialsize = partialcnt * sizeof(struct dinode);
542 	if (partialcnt != 0) {
543 		readpercg++;
544 	} else {
545 		partialcnt = fullcnt;
546 		partialsize = inobufsize;
547 	}
548 	if (inodebuf == NULL &&
549 	   (inodebuf = malloc((u_int)inobufsize)) == NULL)
550 		err("%s", strerror(errno));
551 	while (nextino < ROOTINO)
552 		getnextinode(nextino);
553 }
554 
555 /*
556  * Free up data structures used to scan inodes.
557  */
558 void
559 freeinodebuf()
560 {
561 
562 	if (inodebuf != NULL)
563 		free(inodebuf);
564 	inodebuf = NULL;
565 }
566 
567 /*
568  * Read specified disk blocks.
569  */
570 void
571 bread(bno, buf, cnt)
572 	daddr_t bno;
573 	char *buf;
574 	long cnt;
575 {
576 
577 	if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 ||
578 	    read(fi, buf, cnt) != cnt)
579 		err("block %ld", bno);
580 }
581 
582 #if __STDC__
583 #include <stdarg.h>
584 #else
585 #include <varargs.h>
586 #endif
587 
588 void
589 #if __STDC__
590 err(const char *fmt, ...)
591 #else
592 err(fmt, va_alist)
593 	char *fmt;
594         va_dcl
595 #endif
596 {
597 	va_list ap;
598 #if __STDC__
599 	va_start(ap, fmt);
600 #else
601 	va_start(ap);
602 #endif
603 	(void)fprintf(stderr, "quotacheck: ");
604 	(void)vfprintf(stderr, fmt, ap);
605 	va_end(ap);
606 	(void)fprintf(stderr, "\n");
607 	exit(1);
608 	/* NOTREACHED */
609 }
610