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 * 4. 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
33#if 0
34#ifndef lint
35static const char copyright[] =
36"@(#) Copyright (c) 1980, 1990, 1993\n\
37 The Regents of the University of California. All rights reserved.\n";
38#endif /* not lint */
39
40#ifndef lint
41static char sccsid[] = "@(#)quotacheck.c 8.3 (Berkeley) 1/29/94";
42#endif /* not lint */
43#endif
44#include <sys/cdefs.h>
45__FBSDID("$FreeBSD: head/sbin/quotacheck/quotacheck.c 166458 2007-02-03 11:20:28Z mpp $");
46
47/*
48 * Fix up / report on disk quotas & usage
49 */
50#include <sys/param.h>
51#include <sys/disklabel.h>
52#include <sys/stat.h>
53
54#include <ufs/ufs/dinode.h>
55#include <ufs/ufs/quota.h>
56#include <ufs/ffs/fs.h>
57
58#include <err.h>
59#include <errno.h>
60#include <fcntl.h>
61#include <fstab.h>
62#include <grp.h>
63#include <pwd.h>
64#include <stdio.h>
65#include <stdlib.h>
66#include <string.h>
67#include <unistd.h>
68
69char *qfname = QUOTAFILENAME;
70char *qfextension[] = INITQFNAMES;
71char *quotagroup = QUOTAGROUP;
72
73union {
74 struct fs sblk;
75 char dummy[MAXBSIZE];
76} sb_un;
77#define sblock sb_un.sblk
78union {
79 struct cg cgblk;
80 char dummy[MAXBSIZE];
81} cg_un;
82#define cgblk cg_un.cgblk
83long dev_bsize = 1;
84ino_t maxino;
85
86union dinode {
87 struct ufs1_dinode dp1;
88 struct ufs2_dinode dp2;
89};
90#define DIP(dp, field) \
91 ((sblock.fs_magic == FS_UFS1_MAGIC) ? \
92 (dp)->dp1.field : (dp)->dp2.field)
93
94struct quotaname {
95 long flags;
96 char grpqfname[PATH_MAX];
97 char usrqfname[PATH_MAX];
98};
99#define HASUSR 1
100#define HASGRP 2
101
102struct fileusage {
103 struct fileusage *fu_next;
104 u_long fu_curinodes;
105 u_long fu_curblocks;
106 u_long fu_id;
107 char fu_name[1];
108 /* actually bigger */
109};
110#define FUHASH 1024 /* must be power of two */
111struct fileusage *fuhead[MAXQUOTAS][FUHASH];
112
113int aflag; /* all file systems */
114int gflag; /* check group quotas */
115int uflag; /* check user quotas */
116int vflag; /* verbose */
117int fi; /* open disk file descriptor */
118
119struct fileusage *
120 addid(u_long, int, char *, char *);
121char *blockcheck(char *);
122void bread(ufs2_daddr_t, char *, long);
123extern int checkfstab(int, int, void * (*)(struct fstab *),
124 int (*)(char *, char *, struct quotaname *));
125int chkquota(char *, char *, struct quotaname *);
126void freeinodebuf(void);
127union dinode *
128 getnextinode(ino_t);
129int getquotagid(void);
130int hasquota(struct fstab *, int, char **);
131struct fileusage *
132 lookup(u_long, int);
133void *needchk(struct fstab *);
134int oneof(char *, char*[], int);
135void printchanges(char *, int, struct dqblk *, struct fileusage *, u_long);
136void setinodebuf(ino_t);
137int update(char *, char *, int);
138void usage(void);
139
140int
141main(argc, argv)
142 int argc;
143 char *argv[];
144{
145 struct fstab *fs;
146 struct passwd *pw;
147 struct group *gr;
148 struct quotaname *auxdata;
149 int i, argnum, maxrun, errs, ch;
150 long done = 0;
151 char *name;
152
153 errs = maxrun = 0;
154 while ((ch = getopt(argc, argv, "aguvl:")) != -1) {
155 switch(ch) {
156 case 'a':
157 aflag++;
158 break;
159 case 'g':
160 gflag++;
161 break;
162 case 'u':
163 uflag++;
164 break;
165 case 'v':
166 vflag++;
167 break;
168 case 'l':
169 maxrun = atoi(optarg);
170 break;
171 default:
172 usage();
173 }
174 }
175 argc -= optind;
176 argv += optind;
177 if ((argc == 0 && !aflag) || (argc > 0 && aflag))
178 usage();
179 if (!gflag && !uflag) {
180 gflag++;
181 uflag++;
182 }
183 if (gflag) {
184 setgrent();
185 while ((gr = getgrent()) != NULL)
186 (void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name,
187 NULL);
188 endgrent();
189 }
190 if (uflag) {
191 setpwent();
192 while ((pw = getpwent()) != NULL)
193 (void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name,
194 NULL);
195 endpwent();
196 }
197 /*
198 * Setting maxrun (-l) makes no sense without the -a flag.
199 * Historically this was never an error, so we just warn.
200 */
201 if (maxrun > 0 && !aflag)
202 warnx("ignoring -l without -a");
203 if (aflag)
204 exit(checkfstab(1, maxrun, needchk, chkquota));
205 if (setfsent() == 0)
206 errx(1, "%s: can't open", FSTAB);
207 while ((fs = getfsent()) != NULL) {
208 if (((argnum = oneof(fs->fs_file, argv, argc)) >= 0 ||
209 (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) &&
210 (auxdata = needchk(fs)) &&
211 (name = blockcheck(fs->fs_spec))) {
212 done |= 1 << argnum;
213 errs += chkquota(name, fs->fs_file, auxdata);
214 }
215 }
216 endfsent();
217 for (i = 0; i < argc; i++)
218 if ((done & (1 << i)) == 0)
219 fprintf(stderr, "%s not found in %s\n",
220 argv[i], FSTAB);
221 exit(errs);
222}
223
224void
225usage()
226{
227 (void)fprintf(stderr, "%s\n%s\n",
228 "usage: quotacheck [-guv] [-l maxrun] -a",
229 " quotacheck [-guv] filesystem ...");
230 exit(1);
231}
232
233void *
234needchk(fs)
235 struct fstab *fs;
236{
237 struct quotaname *qnp;
238 char *qfnp;
239
240 if (strcmp(fs->fs_vfstype, "ufs") ||
241 strcmp(fs->fs_type, FSTAB_RW))
242 return (NULL);
243 if ((qnp = malloc(sizeof(*qnp))) == NULL)
244 errx(1, "malloc failed");
245 qnp->flags = 0;
246 if (gflag && hasquota(fs, GRPQUOTA, &qfnp)) {
247 strcpy(qnp->grpqfname, qfnp);
248 qnp->flags |= HASGRP;
249 }
250 if (uflag && hasquota(fs, USRQUOTA, &qfnp)) {
251 strcpy(qnp->usrqfname, qfnp);
252 qnp->flags |= HASUSR;
253 }
254 if (qnp->flags)
255 return (qnp);
256 free(qnp);
257 return (NULL);
258}
259
260/*
261 * Possible superblock locations ordered from most to least likely.
262 */
263static int sblock_try[] = SBLOCKSEARCH;
264
265/*
266 * Scan the specified file system to check quota(s) present on it.
267 */
268int
269chkquota(fsname, mntpt, qnp)
270 char *fsname, *mntpt;
271 struct quotaname *qnp;
272{
273 struct fileusage *fup;
274 union dinode *dp;
275 int cg, i, mode, errs = 0;
276 ino_t ino, inosused, userino = 0, groupino = 0;
277 char *cp;
278 struct stat sb;
279
280 if ((fi = open(fsname, O_RDONLY, 0)) < 0) {
281 warn("%s", fsname);
282 return (1);
283 }
284 if (vflag) {
285 (void)printf("*** Checking ");
286 if (qnp->flags & HASUSR)
287 (void)printf("%s%s", qfextension[USRQUOTA],
288 (qnp->flags & HASGRP) ? " and " : "");
289 if (qnp->flags & HASGRP)
290 (void)printf("%s", qfextension[GRPQUOTA]);
291 (void)printf(" quotas for %s (%s)\n", fsname, mntpt);
292 }
293 if (qnp->flags & HASUSR) {
294 if (stat(qnp->usrqfname, &sb) == 0)
295 userino = sb.st_ino;
296 }
297 if (qnp->flags & HASGRP) {
298 if (stat(qnp->grpqfname, &sb) == 0)
299 groupino = sb.st_ino;
300 }
301 sync();
302 dev_bsize = 1;
303 for (i = 0; sblock_try[i] != -1; i++) {
304 bread(sblock_try[i], (char *)&sblock, (long)SBLOCKSIZE);
305 if ((sblock.fs_magic == FS_UFS1_MAGIC ||
306 (sblock.fs_magic == FS_UFS2_MAGIC &&
307 sblock.fs_sblockloc == sblock_try[i])) &&
308 sblock.fs_bsize <= MAXBSIZE &&
309 sblock.fs_bsize >= sizeof(struct fs))
310 break;
311 }
312 if (sblock_try[i] == -1) {
313 warn("Cannot find file system superblock");
314 return (1);
315 }
316 dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1);
317 maxino = sblock.fs_ncg * sblock.fs_ipg;
318 for (cg = 0; cg < sblock.fs_ncg; cg++) {
319 ino = cg * sblock.fs_ipg;
320 setinodebuf(ino);
321 bread(fsbtodb(&sblock, cgtod(&sblock, cg)), (char *)(&cgblk),
322 sblock.fs_cgsize);
323 if (sblock.fs_magic == FS_UFS2_MAGIC)
324 inosused = cgblk.cg_initediblk;
325 else
326 inosused = sblock.fs_ipg;
327 /*
328 * If we are using soft updates, then we can trust the
329 * cylinder group inode allocation maps to tell us which
330 * inodes are allocated. We will scan the used inode map
331 * to find the inodes that are really in use, and then
332 * read only those inodes in from disk.
333 */
334 if (sblock.fs_flags & FS_DOSOFTDEP) {
335 if (!cg_chkmagic(&cgblk))
336 errx(1, "CG %d: BAD MAGIC NUMBER\n", cg);
337 cp = &cg_inosused(&cgblk)[(inosused - 1) / CHAR_BIT];
338 for ( ; inosused > 0; inosused -= CHAR_BIT, cp--) {
339 if (*cp == 0)
340 continue;
341 for (i = 1 << (CHAR_BIT - 1); i > 0; i >>= 1) {
342 if (*cp & i)
343 break;
344 inosused--;
345 }
346 break;
347 }
348 if (inosused <= 0)
349 continue;
350 }
351 for (i = 0; i < inosused; i++, ino++) {
352 if ((dp = getnextinode(ino)) == NULL || ino < ROOTINO ||
353 (mode = DIP(dp, di_mode) & IFMT) == 0)
354 continue;
355 /*
356 * XXX: Do not account for UIDs or GIDs that appear
357 * to be negative to prevent generating 100GB+
358 * quota files.
359 */
360 if ((int)DIP(dp, di_uid) < 0 ||
361 (int)DIP(dp, di_gid) < 0) {
362 if (vflag) {
363 if (aflag)
364 (void)printf("%s: ", mntpt);
365 (void)printf("out of range UID/GID (%u/%u) ino=%u\n",
366 DIP(dp, di_uid), DIP(dp,di_gid),
367 ino);
368 }
369 continue;
370 }
371
372 /*
373 * Do not account for file system snapshot files
374 * or the actual quota data files to be consistent
375 * with how they are handled inside the kernel.
376 */
377#ifdef SF_SNAPSHOT
378 if (DIP(dp, di_flags) & SF_SNAPSHOT)
379 continue;
380#endif
381 if (ino == userino || ino == groupino)
382 continue;
383 if (qnp->flags & HASGRP) {
384 fup = addid((u_long)DIP(dp, di_gid), GRPQUOTA,
385 (char *)0, mntpt);
386 fup->fu_curinodes++;
387 if (mode == IFREG || mode == IFDIR ||
388 mode == IFLNK)
389 fup->fu_curblocks += DIP(dp, di_blocks);
390 }
391 if (qnp->flags & HASUSR) {
392 fup = addid((u_long)DIP(dp, di_uid), USRQUOTA,
393 (char *)0, mntpt);
394 fup->fu_curinodes++;
395 if (mode == IFREG || mode == IFDIR ||
396 mode == IFLNK)
397 fup->fu_curblocks += DIP(dp, di_blocks);
398 }
399 }
400 }
401 freeinodebuf();
402 if (qnp->flags & HASUSR)
403 errs += update(mntpt, qnp->usrqfname, USRQUOTA);
404 if (qnp->flags & HASGRP)
405 errs += update(mntpt, qnp->grpqfname, GRPQUOTA);
406 close(fi);
407 return (errs);
408}
409
410/*
411 * Update a specified quota file.
412 */
413int
414update(fsname, quotafile, type)
415 char *fsname, *quotafile;
416 int type;
417{
418 struct fileusage *fup;
419 FILE *qfi, *qfo;
420 u_long id, lastid, highid = 0;
421 off_t offset;
422 int i;
423 struct dqblk dqbuf;
424 struct stat sb;
425 static int warned = 0;
426 static struct dqblk zerodqbuf;
427 static struct fileusage zerofileusage;
428
429 if ((qfo = fopen(quotafile, "r+")) == NULL) {
430 if (errno == ENOENT)
431 qfo = fopen(quotafile, "w+");
432 if (qfo) {
433 warnx("creating quota file %s", quotafile);
434#define MODE (S_IRUSR|S_IWUSR|S_IRGRP)
435 (void) fchown(fileno(qfo), getuid(), getquotagid());
436 (void) fchmod(fileno(qfo), MODE);
437 } else {
438 warn("%s", quotafile);
439 return (1);
440 }
441 }
442 if ((qfi = fopen(quotafile, "r")) == NULL) {
443 warn("%s", quotafile);
444 (void) fclose(qfo);
445 return (1);
446 }
447 if (quotactl(fsname, QCMD(Q_SYNC, type), (u_long)0, (caddr_t)0) < 0 &&
448 errno == EOPNOTSUPP && !warned && vflag) {
449 warned++;
450 (void)printf("*** Warning: %s\n",
451 "Quotas are not compiled into this kernel");
452 }
453 if (fstat(fileno(qfi), &sb) < 0) {
454 warn("Cannot fstat quota file %s\n", quotafile);
455 (void) fclose(qfo);
456 (void) fclose(qfi);
457 return (1);
458 }
459 if ((sb.st_size % sizeof(struct dqblk)) != 0)
460 warn("%s size is not a multiple of dqblk\n", quotafile);
461
462 /*
463 * Scan the on-disk quota file and record any usage changes.
464 */
465
466 if (sb.st_size != 0)
467 lastid = (sb.st_size / sizeof(struct dqblk)) - 1;
468 else
469 lastid = 0;
470 for (id = 0, offset = 0; id <= lastid;
471 id++, offset += sizeof(struct dqblk)) {
472 if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0)
473 dqbuf = zerodqbuf;
474 if ((fup = lookup(id, type)) == NULL)
475 fup = &zerofileusage;
476 if (fup->fu_curinodes || fup->fu_curblocks ||
477 dqbuf.dqb_bsoftlimit || dqbuf.dqb_bhardlimit ||
478 dqbuf.dqb_isoftlimit || dqbuf.dqb_ihardlimit)
479 highid = id;
480 if (dqbuf.dqb_curinodes == fup->fu_curinodes &&
481 dqbuf.dqb_curblocks == fup->fu_curblocks) {
482 fup->fu_curinodes = 0;
483 fup->fu_curblocks = 0;
484 continue;
485 }
486 printchanges(fsname, type, &dqbuf, fup, id);
487 /*
488 * Reset time limit if have a soft limit and were
489 * previously under it, but are now over it.
490 */
491 if (dqbuf.dqb_bsoftlimit && id != 0 &&
492 dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
493 fup->fu_curblocks >= dqbuf.dqb_bsoftlimit)
494 dqbuf.dqb_btime = 0;
495 if (dqbuf.dqb_isoftlimit && id != 0 &&
496 dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
497 fup->fu_curinodes >= dqbuf.dqb_isoftlimit)
498 dqbuf.dqb_itime = 0;
499 dqbuf.dqb_curinodes = fup->fu_curinodes;
500 dqbuf.dqb_curblocks = fup->fu_curblocks;
501 if (fseeko(qfo, offset, SEEK_SET) < 0) {
502 warn("%s: seek failed", quotafile);
503 return(1);
504 }
505 fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo);
506 (void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
507 (caddr_t)&dqbuf);
508 fup->fu_curinodes = 0;
509 fup->fu_curblocks = 0;
510 }
511
512 /*
513 * Walk the hash table looking for ids with non-zero usage
514 * that are not currently recorded in the quota file. E.g.
515 * ids that are past the end of the current file.
516 */
517
518 for (i = 0; i < FUHASH; i++) {
519 for (fup = fuhead[type][i]; fup != NULL; fup = fup->fu_next) {
520 if (fup->fu_id <= lastid)
521 continue;
522 if (fup->fu_curinodes == 0 && fup->fu_curblocks == 0)
523 continue;
524 bzero(&dqbuf, sizeof(struct dqblk));
525 if (fup->fu_id > highid)
526 highid = fup->fu_id;
527 printchanges(fsname, type, &dqbuf, fup, id);
528 dqbuf.dqb_curinodes = fup->fu_curinodes;
529 dqbuf.dqb_curblocks = fup->fu_curblocks;
530 offset = (off_t)fup->fu_id * sizeof(struct dqblk);
531 if (fseeko(qfo, offset, SEEK_SET) < 0) {
532 warn("%s: seek failed", quotafile);
533 return(1);
534 }
535 fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo);
536 (void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
537 (caddr_t)&dqbuf);
538 fup->fu_curinodes = 0;
539 fup->fu_curblocks = 0;
540 }
541 }
542 fclose(qfi);
543 fflush(qfo);
544 ftruncate(fileno(qfo),
545 (((off_t)highid + 1) * sizeof(struct dqblk)));
546 fclose(qfo);
547 return (0);
548}
549
550/*
551 * Check to see if target appears in list of size cnt.
552 */
553int
554oneof(target, list, cnt)
555 char *target, *list[];
556 int cnt;
557{
558 int i;
559
560 for (i = 0; i < cnt; i++)
561 if (strcmp(target, list[i]) == 0)
562 return (i);
563 return (-1);
564}
565
566/*
567 * Determine the group identifier for quota files.
568 */
569int
570getquotagid()
571{
572 struct group *gr;
573
574 if ((gr = getgrnam(quotagroup)) != NULL)
575 return (gr->gr_gid);
576 return (-1);
577}
578
579/*
580 * Check to see if a particular quota is to be enabled.
581 */
582int
583hasquota(fs, type, qfnamep)
584 struct fstab *fs;
585 int type;
586 char **qfnamep;
587{
588 char *opt;
589 char *cp;
590 static char initname, usrname[100], grpname[100];
591 static char buf[BUFSIZ];
592
593 if (!initname) {
594 (void)snprintf(usrname, sizeof(usrname),
595 "%s%s", qfextension[USRQUOTA], qfname);
596 (void)snprintf(grpname, sizeof(grpname),
597 "%s%s", qfextension[GRPQUOTA], qfname);
598 initname = 1;
599 }
600 strcpy(buf, fs->fs_mntops);
601 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
602 if ((cp = index(opt, '=')) != NULL)
603 *cp++ = '\0';
604 if (type == USRQUOTA && strcmp(opt, usrname) == 0)
605 break;
606 if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
607 break;
608 }
609 if (!opt)
610 return (0);
611 if (cp)
612 *qfnamep = cp;
613 else {
614 (void)snprintf(buf, sizeof(buf),
615 "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
616 *qfnamep = buf;
617 }
618 return (1);
619}
620
621/*
622 * Routines to manage the file usage table.
623 *
624 * Lookup an id of a specific type.
625 */
626struct fileusage *
627lookup(id, type)
628 u_long id;
629 int type;
630{
631 struct fileusage *fup;
632
633 for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next)
634 if (fup->fu_id == id)
635 return (fup);
636 return (NULL);
637}
638
639/*
640 * Add a new file usage id if it does not already exist.
641 */
642struct fileusage *
643addid(id, type, name, fsname)
644 u_long id;
645 int type;
646 char *name;
647 char *fsname;
648{
649 struct fileusage *fup, **fhp;
650 int len;
651
652 if ((fup = lookup(id, type)) != NULL)
653 return (fup);
654 if (name)
655 len = strlen(name);
656 else
657 len = 0;
658 if ((fup = calloc(1, sizeof(*fup) + len)) == NULL)
659 errx(1, "calloc failed");
660 fhp = &fuhead[type][id & (FUHASH - 1)];
661 fup->fu_next = *fhp;
662 *fhp = fup;
663 fup->fu_id = id;
664 if (name)
665 bcopy(name, fup->fu_name, len + 1);
666 else {
667 (void)sprintf(fup->fu_name, "%lu", id);
668 if (vflag) {
669 if (aflag && fsname != NULL)
670 (void)printf("%s: ", fsname);
671 printf("unknown %cid: %lu\n",
672 type == USRQUOTA ? 'u' : 'g', id);
673 }
674 }
675 return (fup);
676}
677
678/*
679 * Special purpose version of ginode used to optimize pass
680 * over all the inodes in numerical order.
681 */
682static ino_t nextino, lastinum, lastvalidinum;
683static long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize;
684static caddr_t inodebuf;
685#define INOBUFSIZE 56*1024 /* size of buffer to read inodes */
686
687union dinode *
688getnextinode(ino_t inumber)
689{
690 long size;
691 ufs2_daddr_t dblk;
692 union dinode *dp;
693 static caddr_t nextinop;
694
695 if (inumber != nextino++ || inumber > lastvalidinum)
696 errx(1, "bad inode number %d to nextinode", inumber);
697 if (inumber >= lastinum) {
698 readcnt++;
699 dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum));
700 if (readcnt % readpercg == 0) {
701 size = partialsize;
702 lastinum += partialcnt;
703 } else {
704 size = inobufsize;
705 lastinum += fullcnt;
706 }
707 /*
708 * If bread returns an error, it will already have zeroed
709 * out the buffer, so we do not need to do so here.
710 */
711 bread(dblk, inodebuf, size);
712 nextinop = inodebuf;
713 }
714 dp = (union dinode *)nextinop;
715 if (sblock.fs_magic == FS_UFS1_MAGIC)
716 nextinop += sizeof(struct ufs1_dinode);
717 else
718 nextinop += sizeof(struct ufs2_dinode);
719 return (dp);
720}
721
722/*
723 * Prepare to scan a set of inodes.
724 */
725void
726setinodebuf(ino_t inum)
727{
728
729 if (inum % sblock.fs_ipg != 0)
730 errx(1, "bad inode number %d to setinodebuf", inum);
731 lastvalidinum = inum + sblock.fs_ipg - 1;
732 nextino = inum;
733 lastinum = inum;
734 readcnt = 0;
735 if (inodebuf != NULL)
736 return;
737 inobufsize = blkroundup(&sblock, INOBUFSIZE);
738 fullcnt = inobufsize / ((sblock.fs_magic == FS_UFS1_MAGIC) ?
739 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode));
740 readpercg = sblock.fs_ipg / fullcnt;
741 partialcnt = sblock.fs_ipg % fullcnt;
742 partialsize = partialcnt * ((sblock.fs_magic == FS_UFS1_MAGIC) ?
743 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode));
744 if (partialcnt != 0) {
745 readpercg++;
746 } else {
747 partialcnt = fullcnt;
748 partialsize = inobufsize;
749 }
750 if ((inodebuf = malloc((unsigned)inobufsize)) == NULL)
751 errx(1, "cannot allocate space for inode buffer");
752}
753
754/*
755 * Free up data structures used to scan inodes.
756 */
757void
758freeinodebuf()
759{
760
761 if (inodebuf != NULL)
762 free(inodebuf);
763 inodebuf = NULL;
764}
765
766/*
767 * Read specified disk blocks.
768 */
769void
770bread(bno, buf, cnt)
771 ufs2_daddr_t bno;
772 char *buf;
773 long cnt;
774{
775
776 if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 ||
777 read(fi, buf, cnt) != cnt)
778 errx(1, "bread failed on block %ld", (long)bno);
779}
780
781/*
782 * Display updated block and i-node counts.
783 */
784void
785printchanges(fsname, type, dp, fup, id)
786 char *fsname;
787 int type;
788 struct dqblk *dp;
789 struct fileusage *fup;
790 u_long id;
791{
792 if (!vflag)
793 return;
794 if (aflag)
795 (void)printf("%s: ", fsname);
796 if (fup->fu_name[0] == '\0')
797 (void)printf("%-8lu fixed ", id);
798 else
799 (void)printf("%-8s fixed ", fup->fu_name);
800 switch (type) {
801
802 case GRPQUOTA:
803 (void)printf("(group):");
804 break;
805
806 case USRQUOTA:
807 (void)printf("(user): ");
808 break;
809
810 default:
811 (void)printf("(unknown quota type %d)", type);
812 break;
813 }
814 if (dp->dqb_curinodes != fup->fu_curinodes)
815 (void)printf("\tinodes %lu -> %lu", (u_long)dp->dqb_curinodes,
816 (u_long)fup->fu_curinodes);
817 if (dp->dqb_curblocks != fup->fu_curblocks)
818 (void)printf("\tblocks %lu -> %lu",
819 (u_long)dp->dqb_curblocks,
820 (u_long)fup->fu_curblocks);
821 (void)printf("\n");
822}
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 * 4. 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
33#if 0
34#ifndef lint
35static const char copyright[] =
36"@(#) Copyright (c) 1980, 1990, 1993\n\
37 The Regents of the University of California. All rights reserved.\n";
38#endif /* not lint */
39
40#ifndef lint
41static char sccsid[] = "@(#)quotacheck.c 8.3 (Berkeley) 1/29/94";
42#endif /* not lint */
43#endif
44#include <sys/cdefs.h>
45__FBSDID("$FreeBSD: head/sbin/quotacheck/quotacheck.c 166458 2007-02-03 11:20:28Z mpp $");
46
47/*
48 * Fix up / report on disk quotas & usage
49 */
50#include <sys/param.h>
51#include <sys/disklabel.h>
52#include <sys/stat.h>
53
54#include <ufs/ufs/dinode.h>
55#include <ufs/ufs/quota.h>
56#include <ufs/ffs/fs.h>
57
58#include <err.h>
59#include <errno.h>
60#include <fcntl.h>
61#include <fstab.h>
62#include <grp.h>
63#include <pwd.h>
64#include <stdio.h>
65#include <stdlib.h>
66#include <string.h>
67#include <unistd.h>
68
69char *qfname = QUOTAFILENAME;
70char *qfextension[] = INITQFNAMES;
71char *quotagroup = QUOTAGROUP;
72
73union {
74 struct fs sblk;
75 char dummy[MAXBSIZE];
76} sb_un;
77#define sblock sb_un.sblk
78union {
79 struct cg cgblk;
80 char dummy[MAXBSIZE];
81} cg_un;
82#define cgblk cg_un.cgblk
83long dev_bsize = 1;
84ino_t maxino;
85
86union dinode {
87 struct ufs1_dinode dp1;
88 struct ufs2_dinode dp2;
89};
90#define DIP(dp, field) \
91 ((sblock.fs_magic == FS_UFS1_MAGIC) ? \
92 (dp)->dp1.field : (dp)->dp2.field)
93
94struct quotaname {
95 long flags;
96 char grpqfname[PATH_MAX];
97 char usrqfname[PATH_MAX];
98};
99#define HASUSR 1
100#define HASGRP 2
101
102struct fileusage {
103 struct fileusage *fu_next;
104 u_long fu_curinodes;
105 u_long fu_curblocks;
106 u_long fu_id;
107 char fu_name[1];
108 /* actually bigger */
109};
110#define FUHASH 1024 /* must be power of two */
111struct fileusage *fuhead[MAXQUOTAS][FUHASH];
112
113int aflag; /* all file systems */
114int gflag; /* check group quotas */
115int uflag; /* check user quotas */
116int vflag; /* verbose */
117int fi; /* open disk file descriptor */
118
119struct fileusage *
120 addid(u_long, int, char *, char *);
121char *blockcheck(char *);
122void bread(ufs2_daddr_t, char *, long);
123extern int checkfstab(int, int, void * (*)(struct fstab *),
124 int (*)(char *, char *, struct quotaname *));
125int chkquota(char *, char *, struct quotaname *);
126void freeinodebuf(void);
127union dinode *
128 getnextinode(ino_t);
129int getquotagid(void);
130int hasquota(struct fstab *, int, char **);
131struct fileusage *
132 lookup(u_long, int);
133void *needchk(struct fstab *);
134int oneof(char *, char*[], int);
135void printchanges(char *, int, struct dqblk *, struct fileusage *, u_long);
136void setinodebuf(ino_t);
137int update(char *, char *, int);
138void usage(void);
139
140int
141main(argc, argv)
142 int argc;
143 char *argv[];
144{
145 struct fstab *fs;
146 struct passwd *pw;
147 struct group *gr;
148 struct quotaname *auxdata;
149 int i, argnum, maxrun, errs, ch;
150 long done = 0;
151 char *name;
152
153 errs = maxrun = 0;
154 while ((ch = getopt(argc, argv, "aguvl:")) != -1) {
155 switch(ch) {
156 case 'a':
157 aflag++;
158 break;
159 case 'g':
160 gflag++;
161 break;
162 case 'u':
163 uflag++;
164 break;
165 case 'v':
166 vflag++;
167 break;
168 case 'l':
169 maxrun = atoi(optarg);
170 break;
171 default:
172 usage();
173 }
174 }
175 argc -= optind;
176 argv += optind;
177 if ((argc == 0 && !aflag) || (argc > 0 && aflag))
178 usage();
179 if (!gflag && !uflag) {
180 gflag++;
181 uflag++;
182 }
183 if (gflag) {
184 setgrent();
185 while ((gr = getgrent()) != NULL)
186 (void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name,
187 NULL);
188 endgrent();
189 }
190 if (uflag) {
191 setpwent();
192 while ((pw = getpwent()) != NULL)
193 (void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name,
194 NULL);
195 endpwent();
196 }
197 /*
198 * Setting maxrun (-l) makes no sense without the -a flag.
199 * Historically this was never an error, so we just warn.
200 */
201 if (maxrun > 0 && !aflag)
202 warnx("ignoring -l without -a");
203 if (aflag)
204 exit(checkfstab(1, maxrun, needchk, chkquota));
205 if (setfsent() == 0)
206 errx(1, "%s: can't open", FSTAB);
207 while ((fs = getfsent()) != NULL) {
208 if (((argnum = oneof(fs->fs_file, argv, argc)) >= 0 ||
209 (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) &&
210 (auxdata = needchk(fs)) &&
211 (name = blockcheck(fs->fs_spec))) {
212 done |= 1 << argnum;
213 errs += chkquota(name, fs->fs_file, auxdata);
214 }
215 }
216 endfsent();
217 for (i = 0; i < argc; i++)
218 if ((done & (1 << i)) == 0)
219 fprintf(stderr, "%s not found in %s\n",
220 argv[i], FSTAB);
221 exit(errs);
222}
223
224void
225usage()
226{
227 (void)fprintf(stderr, "%s\n%s\n",
228 "usage: quotacheck [-guv] [-l maxrun] -a",
229 " quotacheck [-guv] filesystem ...");
230 exit(1);
231}
232
233void *
234needchk(fs)
235 struct fstab *fs;
236{
237 struct quotaname *qnp;
238 char *qfnp;
239
240 if (strcmp(fs->fs_vfstype, "ufs") ||
241 strcmp(fs->fs_type, FSTAB_RW))
242 return (NULL);
243 if ((qnp = malloc(sizeof(*qnp))) == NULL)
244 errx(1, "malloc failed");
245 qnp->flags = 0;
246 if (gflag && hasquota(fs, GRPQUOTA, &qfnp)) {
247 strcpy(qnp->grpqfname, qfnp);
248 qnp->flags |= HASGRP;
249 }
250 if (uflag && hasquota(fs, USRQUOTA, &qfnp)) {
251 strcpy(qnp->usrqfname, qfnp);
252 qnp->flags |= HASUSR;
253 }
254 if (qnp->flags)
255 return (qnp);
256 free(qnp);
257 return (NULL);
258}
259
260/*
261 * Possible superblock locations ordered from most to least likely.
262 */
263static int sblock_try[] = SBLOCKSEARCH;
264
265/*
266 * Scan the specified file system to check quota(s) present on it.
267 */
268int
269chkquota(fsname, mntpt, qnp)
270 char *fsname, *mntpt;
271 struct quotaname *qnp;
272{
273 struct fileusage *fup;
274 union dinode *dp;
275 int cg, i, mode, errs = 0;
276 ino_t ino, inosused, userino = 0, groupino = 0;
277 char *cp;
278 struct stat sb;
279
280 if ((fi = open(fsname, O_RDONLY, 0)) < 0) {
281 warn("%s", fsname);
282 return (1);
283 }
284 if (vflag) {
285 (void)printf("*** Checking ");
286 if (qnp->flags & HASUSR)
287 (void)printf("%s%s", qfextension[USRQUOTA],
288 (qnp->flags & HASGRP) ? " and " : "");
289 if (qnp->flags & HASGRP)
290 (void)printf("%s", qfextension[GRPQUOTA]);
291 (void)printf(" quotas for %s (%s)\n", fsname, mntpt);
292 }
293 if (qnp->flags & HASUSR) {
294 if (stat(qnp->usrqfname, &sb) == 0)
295 userino = sb.st_ino;
296 }
297 if (qnp->flags & HASGRP) {
298 if (stat(qnp->grpqfname, &sb) == 0)
299 groupino = sb.st_ino;
300 }
301 sync();
302 dev_bsize = 1;
303 for (i = 0; sblock_try[i] != -1; i++) {
304 bread(sblock_try[i], (char *)&sblock, (long)SBLOCKSIZE);
305 if ((sblock.fs_magic == FS_UFS1_MAGIC ||
306 (sblock.fs_magic == FS_UFS2_MAGIC &&
307 sblock.fs_sblockloc == sblock_try[i])) &&
308 sblock.fs_bsize <= MAXBSIZE &&
309 sblock.fs_bsize >= sizeof(struct fs))
310 break;
311 }
312 if (sblock_try[i] == -1) {
313 warn("Cannot find file system superblock");
314 return (1);
315 }
316 dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1);
317 maxino = sblock.fs_ncg * sblock.fs_ipg;
318 for (cg = 0; cg < sblock.fs_ncg; cg++) {
319 ino = cg * sblock.fs_ipg;
320 setinodebuf(ino);
321 bread(fsbtodb(&sblock, cgtod(&sblock, cg)), (char *)(&cgblk),
322 sblock.fs_cgsize);
323 if (sblock.fs_magic == FS_UFS2_MAGIC)
324 inosused = cgblk.cg_initediblk;
325 else
326 inosused = sblock.fs_ipg;
327 /*
328 * If we are using soft updates, then we can trust the
329 * cylinder group inode allocation maps to tell us which
330 * inodes are allocated. We will scan the used inode map
331 * to find the inodes that are really in use, and then
332 * read only those inodes in from disk.
333 */
334 if (sblock.fs_flags & FS_DOSOFTDEP) {
335 if (!cg_chkmagic(&cgblk))
336 errx(1, "CG %d: BAD MAGIC NUMBER\n", cg);
337 cp = &cg_inosused(&cgblk)[(inosused - 1) / CHAR_BIT];
338 for ( ; inosused > 0; inosused -= CHAR_BIT, cp--) {
339 if (*cp == 0)
340 continue;
341 for (i = 1 << (CHAR_BIT - 1); i > 0; i >>= 1) {
342 if (*cp & i)
343 break;
344 inosused--;
345 }
346 break;
347 }
348 if (inosused <= 0)
349 continue;
350 }
351 for (i = 0; i < inosused; i++, ino++) {
352 if ((dp = getnextinode(ino)) == NULL || ino < ROOTINO ||
353 (mode = DIP(dp, di_mode) & IFMT) == 0)
354 continue;
355 /*
356 * XXX: Do not account for UIDs or GIDs that appear
357 * to be negative to prevent generating 100GB+
358 * quota files.
359 */
360 if ((int)DIP(dp, di_uid) < 0 ||
361 (int)DIP(dp, di_gid) < 0) {
362 if (vflag) {
363 if (aflag)
364 (void)printf("%s: ", mntpt);
365 (void)printf("out of range UID/GID (%u/%u) ino=%u\n",
366 DIP(dp, di_uid), DIP(dp,di_gid),
367 ino);
368 }
369 continue;
370 }
371
372 /*
373 * Do not account for file system snapshot files
374 * or the actual quota data files to be consistent
375 * with how they are handled inside the kernel.
376 */
377#ifdef SF_SNAPSHOT
378 if (DIP(dp, di_flags) & SF_SNAPSHOT)
379 continue;
380#endif
381 if (ino == userino || ino == groupino)
382 continue;
383 if (qnp->flags & HASGRP) {
384 fup = addid((u_long)DIP(dp, di_gid), GRPQUOTA,
385 (char *)0, mntpt);
386 fup->fu_curinodes++;
387 if (mode == IFREG || mode == IFDIR ||
388 mode == IFLNK)
389 fup->fu_curblocks += DIP(dp, di_blocks);
390 }
391 if (qnp->flags & HASUSR) {
392 fup = addid((u_long)DIP(dp, di_uid), USRQUOTA,
393 (char *)0, mntpt);
394 fup->fu_curinodes++;
395 if (mode == IFREG || mode == IFDIR ||
396 mode == IFLNK)
397 fup->fu_curblocks += DIP(dp, di_blocks);
398 }
399 }
400 }
401 freeinodebuf();
402 if (qnp->flags & HASUSR)
403 errs += update(mntpt, qnp->usrqfname, USRQUOTA);
404 if (qnp->flags & HASGRP)
405 errs += update(mntpt, qnp->grpqfname, GRPQUOTA);
406 close(fi);
407 return (errs);
408}
409
410/*
411 * Update a specified quota file.
412 */
413int
414update(fsname, quotafile, type)
415 char *fsname, *quotafile;
416 int type;
417{
418 struct fileusage *fup;
419 FILE *qfi, *qfo;
420 u_long id, lastid, highid = 0;
421 off_t offset;
422 int i;
423 struct dqblk dqbuf;
424 struct stat sb;
425 static int warned = 0;
426 static struct dqblk zerodqbuf;
427 static struct fileusage zerofileusage;
428
429 if ((qfo = fopen(quotafile, "r+")) == NULL) {
430 if (errno == ENOENT)
431 qfo = fopen(quotafile, "w+");
432 if (qfo) {
433 warnx("creating quota file %s", quotafile);
434#define MODE (S_IRUSR|S_IWUSR|S_IRGRP)
435 (void) fchown(fileno(qfo), getuid(), getquotagid());
436 (void) fchmod(fileno(qfo), MODE);
437 } else {
438 warn("%s", quotafile);
439 return (1);
440 }
441 }
442 if ((qfi = fopen(quotafile, "r")) == NULL) {
443 warn("%s", quotafile);
444 (void) fclose(qfo);
445 return (1);
446 }
447 if (quotactl(fsname, QCMD(Q_SYNC, type), (u_long)0, (caddr_t)0) < 0 &&
448 errno == EOPNOTSUPP && !warned && vflag) {
449 warned++;
450 (void)printf("*** Warning: %s\n",
451 "Quotas are not compiled into this kernel");
452 }
453 if (fstat(fileno(qfi), &sb) < 0) {
454 warn("Cannot fstat quota file %s\n", quotafile);
455 (void) fclose(qfo);
456 (void) fclose(qfi);
457 return (1);
458 }
459 if ((sb.st_size % sizeof(struct dqblk)) != 0)
460 warn("%s size is not a multiple of dqblk\n", quotafile);
461
462 /*
463 * Scan the on-disk quota file and record any usage changes.
464 */
465
466 if (sb.st_size != 0)
467 lastid = (sb.st_size / sizeof(struct dqblk)) - 1;
468 else
469 lastid = 0;
470 for (id = 0, offset = 0; id <= lastid;
471 id++, offset += sizeof(struct dqblk)) {
472 if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0)
473 dqbuf = zerodqbuf;
474 if ((fup = lookup(id, type)) == NULL)
475 fup = &zerofileusage;
476 if (fup->fu_curinodes || fup->fu_curblocks ||
477 dqbuf.dqb_bsoftlimit || dqbuf.dqb_bhardlimit ||
478 dqbuf.dqb_isoftlimit || dqbuf.dqb_ihardlimit)
479 highid = id;
480 if (dqbuf.dqb_curinodes == fup->fu_curinodes &&
481 dqbuf.dqb_curblocks == fup->fu_curblocks) {
482 fup->fu_curinodes = 0;
483 fup->fu_curblocks = 0;
484 continue;
485 }
486 printchanges(fsname, type, &dqbuf, fup, id);
487 /*
488 * Reset time limit if have a soft limit and were
489 * previously under it, but are now over it.
490 */
491 if (dqbuf.dqb_bsoftlimit && id != 0 &&
492 dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
493 fup->fu_curblocks >= dqbuf.dqb_bsoftlimit)
494 dqbuf.dqb_btime = 0;
495 if (dqbuf.dqb_isoftlimit && id != 0 &&
496 dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
497 fup->fu_curinodes >= dqbuf.dqb_isoftlimit)
498 dqbuf.dqb_itime = 0;
499 dqbuf.dqb_curinodes = fup->fu_curinodes;
500 dqbuf.dqb_curblocks = fup->fu_curblocks;
501 if (fseeko(qfo, offset, SEEK_SET) < 0) {
502 warn("%s: seek failed", quotafile);
503 return(1);
504 }
505 fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo);
506 (void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
507 (caddr_t)&dqbuf);
508 fup->fu_curinodes = 0;
509 fup->fu_curblocks = 0;
510 }
511
512 /*
513 * Walk the hash table looking for ids with non-zero usage
514 * that are not currently recorded in the quota file. E.g.
515 * ids that are past the end of the current file.
516 */
517
518 for (i = 0; i < FUHASH; i++) {
519 for (fup = fuhead[type][i]; fup != NULL; fup = fup->fu_next) {
520 if (fup->fu_id <= lastid)
521 continue;
522 if (fup->fu_curinodes == 0 && fup->fu_curblocks == 0)
523 continue;
524 bzero(&dqbuf, sizeof(struct dqblk));
525 if (fup->fu_id > highid)
526 highid = fup->fu_id;
527 printchanges(fsname, type, &dqbuf, fup, id);
528 dqbuf.dqb_curinodes = fup->fu_curinodes;
529 dqbuf.dqb_curblocks = fup->fu_curblocks;
530 offset = (off_t)fup->fu_id * sizeof(struct dqblk);
531 if (fseeko(qfo, offset, SEEK_SET) < 0) {
532 warn("%s: seek failed", quotafile);
533 return(1);
534 }
535 fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo);
536 (void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
537 (caddr_t)&dqbuf);
538 fup->fu_curinodes = 0;
539 fup->fu_curblocks = 0;
540 }
541 }
542 fclose(qfi);
543 fflush(qfo);
544 ftruncate(fileno(qfo),
545 (((off_t)highid + 1) * sizeof(struct dqblk)));
546 fclose(qfo);
547 return (0);
548}
549
550/*
551 * Check to see if target appears in list of size cnt.
552 */
553int
554oneof(target, list, cnt)
555 char *target, *list[];
556 int cnt;
557{
558 int i;
559
560 for (i = 0; i < cnt; i++)
561 if (strcmp(target, list[i]) == 0)
562 return (i);
563 return (-1);
564}
565
566/*
567 * Determine the group identifier for quota files.
568 */
569int
570getquotagid()
571{
572 struct group *gr;
573
574 if ((gr = getgrnam(quotagroup)) != NULL)
575 return (gr->gr_gid);
576 return (-1);
577}
578
579/*
580 * Check to see if a particular quota is to be enabled.
581 */
582int
583hasquota(fs, type, qfnamep)
584 struct fstab *fs;
585 int type;
586 char **qfnamep;
587{
588 char *opt;
589 char *cp;
590 static char initname, usrname[100], grpname[100];
591 static char buf[BUFSIZ];
592
593 if (!initname) {
594 (void)snprintf(usrname, sizeof(usrname),
595 "%s%s", qfextension[USRQUOTA], qfname);
596 (void)snprintf(grpname, sizeof(grpname),
597 "%s%s", qfextension[GRPQUOTA], qfname);
598 initname = 1;
599 }
600 strcpy(buf, fs->fs_mntops);
601 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
602 if ((cp = index(opt, '=')) != NULL)
603 *cp++ = '\0';
604 if (type == USRQUOTA && strcmp(opt, usrname) == 0)
605 break;
606 if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
607 break;
608 }
609 if (!opt)
610 return (0);
611 if (cp)
612 *qfnamep = cp;
613 else {
614 (void)snprintf(buf, sizeof(buf),
615 "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
616 *qfnamep = buf;
617 }
618 return (1);
619}
620
621/*
622 * Routines to manage the file usage table.
623 *
624 * Lookup an id of a specific type.
625 */
626struct fileusage *
627lookup(id, type)
628 u_long id;
629 int type;
630{
631 struct fileusage *fup;
632
633 for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next)
634 if (fup->fu_id == id)
635 return (fup);
636 return (NULL);
637}
638
639/*
640 * Add a new file usage id if it does not already exist.
641 */
642struct fileusage *
643addid(id, type, name, fsname)
644 u_long id;
645 int type;
646 char *name;
647 char *fsname;
648{
649 struct fileusage *fup, **fhp;
650 int len;
651
652 if ((fup = lookup(id, type)) != NULL)
653 return (fup);
654 if (name)
655 len = strlen(name);
656 else
657 len = 0;
658 if ((fup = calloc(1, sizeof(*fup) + len)) == NULL)
659 errx(1, "calloc failed");
660 fhp = &fuhead[type][id & (FUHASH - 1)];
661 fup->fu_next = *fhp;
662 *fhp = fup;
663 fup->fu_id = id;
664 if (name)
665 bcopy(name, fup->fu_name, len + 1);
666 else {
667 (void)sprintf(fup->fu_name, "%lu", id);
668 if (vflag) {
669 if (aflag && fsname != NULL)
670 (void)printf("%s: ", fsname);
671 printf("unknown %cid: %lu\n",
672 type == USRQUOTA ? 'u' : 'g', id);
673 }
674 }
675 return (fup);
676}
677
678/*
679 * Special purpose version of ginode used to optimize pass
680 * over all the inodes in numerical order.
681 */
682static ino_t nextino, lastinum, lastvalidinum;
683static long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize;
684static caddr_t inodebuf;
685#define INOBUFSIZE 56*1024 /* size of buffer to read inodes */
686
687union dinode *
688getnextinode(ino_t inumber)
689{
690 long size;
691 ufs2_daddr_t dblk;
692 union dinode *dp;
693 static caddr_t nextinop;
694
695 if (inumber != nextino++ || inumber > lastvalidinum)
696 errx(1, "bad inode number %d to nextinode", inumber);
697 if (inumber >= lastinum) {
698 readcnt++;
699 dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum));
700 if (readcnt % readpercg == 0) {
701 size = partialsize;
702 lastinum += partialcnt;
703 } else {
704 size = inobufsize;
705 lastinum += fullcnt;
706 }
707 /*
708 * If bread returns an error, it will already have zeroed
709 * out the buffer, so we do not need to do so here.
710 */
711 bread(dblk, inodebuf, size);
712 nextinop = inodebuf;
713 }
714 dp = (union dinode *)nextinop;
715 if (sblock.fs_magic == FS_UFS1_MAGIC)
716 nextinop += sizeof(struct ufs1_dinode);
717 else
718 nextinop += sizeof(struct ufs2_dinode);
719 return (dp);
720}
721
722/*
723 * Prepare to scan a set of inodes.
724 */
725void
726setinodebuf(ino_t inum)
727{
728
729 if (inum % sblock.fs_ipg != 0)
730 errx(1, "bad inode number %d to setinodebuf", inum);
731 lastvalidinum = inum + sblock.fs_ipg - 1;
732 nextino = inum;
733 lastinum = inum;
734 readcnt = 0;
735 if (inodebuf != NULL)
736 return;
737 inobufsize = blkroundup(&sblock, INOBUFSIZE);
738 fullcnt = inobufsize / ((sblock.fs_magic == FS_UFS1_MAGIC) ?
739 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode));
740 readpercg = sblock.fs_ipg / fullcnt;
741 partialcnt = sblock.fs_ipg % fullcnt;
742 partialsize = partialcnt * ((sblock.fs_magic == FS_UFS1_MAGIC) ?
743 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode));
744 if (partialcnt != 0) {
745 readpercg++;
746 } else {
747 partialcnt = fullcnt;
748 partialsize = inobufsize;
749 }
750 if ((inodebuf = malloc((unsigned)inobufsize)) == NULL)
751 errx(1, "cannot allocate space for inode buffer");
752}
753
754/*
755 * Free up data structures used to scan inodes.
756 */
757void
758freeinodebuf()
759{
760
761 if (inodebuf != NULL)
762 free(inodebuf);
763 inodebuf = NULL;
764}
765
766/*
767 * Read specified disk blocks.
768 */
769void
770bread(bno, buf, cnt)
771 ufs2_daddr_t bno;
772 char *buf;
773 long cnt;
774{
775
776 if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 ||
777 read(fi, buf, cnt) != cnt)
778 errx(1, "bread failed on block %ld", (long)bno);
779}
780
781/*
782 * Display updated block and i-node counts.
783 */
784void
785printchanges(fsname, type, dp, fup, id)
786 char *fsname;
787 int type;
788 struct dqblk *dp;
789 struct fileusage *fup;
790 u_long id;
791{
792 if (!vflag)
793 return;
794 if (aflag)
795 (void)printf("%s: ", fsname);
796 if (fup->fu_name[0] == '\0')
797 (void)printf("%-8lu fixed ", id);
798 else
799 (void)printf("%-8s fixed ", fup->fu_name);
800 switch (type) {
801
802 case GRPQUOTA:
803 (void)printf("(group):");
804 break;
805
806 case USRQUOTA:
807 (void)printf("(user): ");
808 break;
809
810 default:
811 (void)printf("(unknown quota type %d)", type);
812 break;
813 }
814 if (dp->dqb_curinodes != fup->fu_curinodes)
815 (void)printf("\tinodes %lu -> %lu", (u_long)dp->dqb_curinodes,
816 (u_long)fup->fu_curinodes);
817 if (dp->dqb_curblocks != fup->fu_curblocks)
818 (void)printf("\tblocks %lu -> %lu",
819 (u_long)dp->dqb_curblocks,
820 (u_long)fup->fu_curblocks);
821 (void)printf("\n");
822}