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