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