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