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