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