2 * Copyright (c) 1980, 1990, 1993
3 * The Regents of the University of California. All rights reserved.
5 * This code is derived from software contributed to Berkeley by
6 * Robert Elz at The University of Melbourne.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
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.
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
35 static const char copyright
[] =
36 "@(#) Copyright (c) 1980, 1990, 1993\n\
37 The Regents of the University of California. All rights reserved.\n";
41 static char sccsid
[] = "@(#)quotacheck.c 8.3 (Berkeley) 1/29/94";
44 #include <sys/cdefs.h>
45 __FBSDID("$FreeBSD$");
48 * Fix up / report on disk quotas & usage
50 #include <sys/param.h>
51 #include <sys/disklabel.h>
52 #include <sys/mount.h>
55 #include <ufs/ufs/dinode.h>
56 #include <ufs/ufs/quota.h>
57 #include <ufs/ffs/fs.h>
72 #include "quotacheck.h"
74 char *qfname
= QUOTAFILENAME
;
75 char *qfextension
[] = INITQFNAMES
;
76 char *quotagroup
= QUOTAGROUP
;
82 #define sblock sb_un.sblk
87 #define cgblk cg_un.cgblk
92 struct ufs1_dinode dp1
;
93 struct ufs2_dinode dp2
;
95 #define DIP(dp, field) \
96 ((sblock.fs_magic == FS_UFS1_MAGIC) ? \
97 (dp)->dp1.field : (dp)->dp2.field)
103 struct fileusage
*fu_next
;
108 /* actually bigger */
110 #define FUHASH 1024 /* must be power of two */
111 struct fileusage
*fuhead
[MAXQUOTAS
][FUHASH
];
113 int aflag
; /* all file systems */
114 int cflag
; /* convert format to 32 or 64 bit size */
115 int gflag
; /* check group quotas */
116 int uflag
; /* check user quotas */
117 int vflag
; /* verbose */
118 int fi
; /* open disk file descriptor */
121 addid(u_long
, int, char *, const char *);
122 void bread(ufs2_daddr_t
, char *, long);
123 void freeinodebuf(void);
126 int getquotagid(void);
129 int oneof(char *, char*[], int);
130 void printchanges(const char *, int, struct dqblk
*, struct fileusage
*,
132 void setinodebuf(ino_t
);
133 int update(const char *, struct quotafile
*, int);
137 main(int argc
, char *argv
[])
142 struct quotafile
*qfu
, *qfg
;
143 int i
, argnum
, maxrun
, errs
, ch
;
148 while ((ch
= getopt(argc
, argv
, "ac:guvl:")) != -1) {
156 cflag
= atoi(optarg
);
168 maxrun
= atoi(optarg
);
176 if ((argc
== 0 && !aflag
) || (argc
> 0 && aflag
))
178 if (cflag
&& cflag
!= 32 && cflag
!= 64)
180 if (!gflag
&& !uflag
) {
186 while ((gr
= getgrent()) != NULL
)
187 (void) addid((u_long
)gr
->gr_gid
, GRPQUOTA
, gr
->gr_name
,
193 while ((pw
= getpwent()) != NULL
)
194 (void) addid((u_long
)pw
->pw_uid
, USRQUOTA
, pw
->pw_name
,
199 * The maxrun (-l) option is now deprecated.
202 warnx("the -l option is now deprecated");
204 exit(checkfstab(uflag
, gflag
));
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 (name
= blockcheck(fs
->fs_spec
))) {
214 qfu
= quota_open(fs
, USRQUOTA
, O_CREAT
|O_RDWR
);
217 qfg
= quota_open(fs
, GRPQUOTA
, O_CREAT
|O_RDWR
);
218 if (qfu
== NULL
&& qfg
== NULL
)
220 errs
+= chkquota(name
, qfu
, qfg
);
228 for (i
= 0; i
< argc
; i
++)
229 if ((done
& (1 << i
)) == 0)
230 fprintf(stderr
, "%s not found in %s\n",
238 (void)fprintf(stderr
, "%s\n%s\n",
239 "usage: quotacheck [-guv] [-c 32 | 64] [-l maxrun] -a",
240 " quotacheck [-guv] [-c 32 | 64] filesystem ...");
245 * Possible superblock locations ordered from most to least likely.
247 static int sblock_try
[] = SBLOCKSEARCH
;
250 * Scan the specified file system to check quota(s) present on it.
253 chkquota(char *specname
, struct quotafile
*qfu
, struct quotafile
*qfg
)
255 struct fileusage
*fup
;
257 int cg
, i
, mode
, errs
= 0;
258 ino_t ino
, inosused
, userino
= 0, groupino
= 0;
259 dev_t dev
, userdev
= 0, groupdev
= 0;
265 mntpt
= quota_fsname(qfu
);
266 else if (qfg
!= NULL
)
267 mntpt
= quota_fsname(qfg
);
269 errx(1, "null quotafile information passed to chkquota()\n");
271 if (vflag
&& qfu
!= NULL
)
272 printf("%s: convert user quota to %d bits\n",
274 if (qfu
!= NULL
&& quota_convert(qfu
, cflag
) < 0) {
277 "%s: cannot convert an active quota file",
279 err(1, "user quota conversion to size %d failed",
282 if (vflag
&& qfg
!= NULL
)
283 printf("%s: convert group quota to %d bits\n",
285 if (qfg
!= NULL
&& quota_convert(qfg
, cflag
) < 0) {
288 "%s: cannot convert an active quota file",
290 err(1, "group quota conversion to size %d failed",
294 if ((fi
= open(specname
, O_RDONLY
, 0)) < 0) {
295 warn("%s", specname
);
298 if ((stat(mntpt
, &sb
)) < 0) {
304 (void)printf("*** Checking ");
306 (void)printf("user%s", qfg
? " and " : "");
308 (void)printf("group");
309 (void)printf(" quotas for %s (%s)\n", specname
, mntpt
);
312 if (stat(quota_qfname(qfu
), &sb
) == 0) {
318 if (stat(quota_qfname(qfg
), &sb
) == 0) {
319 groupino
= sb
.st_ino
;
320 groupdev
= sb
.st_dev
;
325 for (i
= 0; sblock_try
[i
] != -1; i
++) {
326 bread(sblock_try
[i
], (char *)&sblock
, (long)SBLOCKSIZE
);
327 if ((sblock
.fs_magic
== FS_UFS1_MAGIC
||
328 (sblock
.fs_magic
== FS_UFS2_MAGIC
&&
329 sblock
.fs_sblockloc
== sblock_try
[i
])) &&
330 sblock
.fs_bsize
<= MAXBSIZE
&&
331 sblock
.fs_bsize
>= sizeof(struct fs
))
334 if (sblock_try
[i
] == -1) {
335 warn("Cannot find file system superblock");
338 dev_bsize
= sblock
.fs_fsize
/ fsbtodb(&sblock
, 1);
339 maxino
= sblock
.fs_ncg
* sblock
.fs_ipg
;
340 for (cg
= 0; cg
< sblock
.fs_ncg
; cg
++) {
341 ino
= cg
* sblock
.fs_ipg
;
343 bread(fsbtodb(&sblock
, cgtod(&sblock
, cg
)), (char *)(&cgblk
),
345 if (sblock
.fs_magic
== FS_UFS2_MAGIC
)
346 inosused
= cgblk
.cg_initediblk
;
348 inosused
= sblock
.fs_ipg
;
350 * If we are using soft updates, then we can trust the
351 * cylinder group inode allocation maps to tell us which
352 * inodes are allocated. We will scan the used inode map
353 * to find the inodes that are really in use, and then
354 * read only those inodes in from disk.
356 if (sblock
.fs_flags
& FS_DOSOFTDEP
) {
357 if (!cg_chkmagic(&cgblk
))
358 errx(1, "CG %d: BAD MAGIC NUMBER\n", cg
);
359 cp
= &cg_inosused(&cgblk
)[(inosused
- 1) / CHAR_BIT
];
360 for ( ; inosused
> 0; inosused
-= CHAR_BIT
, cp
--) {
363 for (i
= 1 << (CHAR_BIT
- 1); i
> 0; i
>>= 1) {
373 for (i
= 0; i
< inosused
; i
++, ino
++) {
374 if ((dp
= getnextinode(ino
)) == NULL
|| ino
< ROOTINO
||
375 (mode
= DIP(dp
, di_mode
) & IFMT
) == 0)
378 * XXX: Do not account for UIDs or GIDs that appear
379 * to be negative to prevent generating 100GB+
382 if ((int)DIP(dp
, di_uid
) < 0 ||
383 (int)DIP(dp
, di_gid
) < 0) {
386 (void)printf("%s: ", mntpt
);
387 (void)printf("out of range UID/GID (%u/%u) ino=%ju\n",
388 DIP(dp
, di_uid
), DIP(dp
,di_gid
),
395 * Do not account for file system snapshot files
396 * or the actual quota data files to be consistent
397 * with how they are handled inside the kernel.
400 if (DIP(dp
, di_flags
) & SF_SNAPSHOT
)
403 if ((ino
== userino
&& dev
== userdev
) ||
404 (ino
== groupino
&& dev
== groupdev
))
407 fup
= addid((u_long
)DIP(dp
, di_gid
), GRPQUOTA
,
410 if (mode
== IFREG
|| mode
== IFDIR
||
412 fup
->fu_curblocks
+= DIP(dp
, di_blocks
);
415 fup
= addid((u_long
)DIP(dp
, di_uid
), USRQUOTA
,
418 if (mode
== IFREG
|| mode
== IFDIR
||
420 fup
->fu_curblocks
+= DIP(dp
, di_blocks
);
426 errs
+= update(mntpt
, qfu
, USRQUOTA
);
428 errs
+= update(mntpt
, qfg
, GRPQUOTA
);
430 (void)fflush(stdout
);
435 * Update a specified quota file.
438 update(const char *fsname
, struct quotafile
*qf
, int type
)
440 struct fileusage
*fup
;
441 u_long id
, lastid
, highid
= 0;
444 static struct dqblk zerodqbuf
;
445 static struct fileusage zerofileusage
;
448 * Scan the on-disk quota file and record any usage changes.
450 lastid
= quota_maxid(qf
);
451 for (id
= 0; id
<= lastid
; id
++) {
452 if (quota_read(qf
, &dqbuf
, id
) < 0)
454 if ((fup
= lookup(id
, type
)) == NULL
)
455 fup
= &zerofileusage
;
456 if (fup
->fu_curinodes
|| fup
->fu_curblocks
||
457 dqbuf
.dqb_bsoftlimit
|| dqbuf
.dqb_bhardlimit
||
458 dqbuf
.dqb_isoftlimit
|| dqbuf
.dqb_ihardlimit
)
460 if (dqbuf
.dqb_curinodes
== fup
->fu_curinodes
&&
461 dqbuf
.dqb_curblocks
== fup
->fu_curblocks
) {
462 fup
->fu_curinodes
= 0;
463 fup
->fu_curblocks
= 0;
466 printchanges(fsname
, type
, &dqbuf
, fup
, id
);
467 dqbuf
.dqb_curinodes
= fup
->fu_curinodes
;
468 dqbuf
.dqb_curblocks
= fup
->fu_curblocks
;
469 (void) quota_write_usage(qf
, &dqbuf
, id
);
470 fup
->fu_curinodes
= 0;
471 fup
->fu_curblocks
= 0;
475 * Walk the hash table looking for ids with non-zero usage
476 * that are not currently recorded in the quota file. E.g.
477 * ids that are past the end of the current file.
479 for (id
= 0; id
< FUHASH
; id
++) {
480 for (fup
= fuhead
[type
][id
]; fup
!= NULL
; fup
= fup
->fu_next
) {
481 if (fup
->fu_id
<= lastid
)
483 if (fup
->fu_curinodes
== 0 && fup
->fu_curblocks
== 0)
485 bzero(&dqbuf
, sizeof(struct dqblk
));
486 if (fup
->fu_id
> highid
)
488 printchanges(fsname
, type
, &dqbuf
, fup
, fup
->fu_id
);
489 dqbuf
.dqb_curinodes
= fup
->fu_curinodes
;
490 dqbuf
.dqb_curblocks
= fup
->fu_curblocks
;
491 (void) quota_write_usage(qf
, &dqbuf
, fup
->fu_id
);
492 fup
->fu_curinodes
= 0;
493 fup
->fu_curblocks
= 0;
497 * If this is old format file, then size may be smaller,
498 * so ensure that we only truncate when it will make things
499 * smaller, and not if it will grow an old format file.
501 if (highid
< lastid
&&
502 stat(quota_qfname(qf
), &sb
) == 0 &&
503 sb
.st_size
> (((off_t
)highid
+ 2) * sizeof(struct dqblk
)))
504 truncate(quota_qfname(qf
),
505 (((off_t
)highid
+ 2) * sizeof(struct dqblk
)));
510 * Check to see if target appears in list of size cnt.
513 oneof(char *target
, char *list
[], int cnt
)
517 for (i
= 0; i
< cnt
; i
++)
518 if (strcmp(target
, list
[i
]) == 0)
524 * Determine the group identifier for quota files.
531 if ((gr
= getgrnam(quotagroup
)) != NULL
)
537 * Routines to manage the file usage table.
539 * Lookup an id of a specific type.
542 lookup(u_long id
, int type
)
544 struct fileusage
*fup
;
546 for (fup
= fuhead
[type
][id
& (FUHASH
-1)]; fup
!= NULL
; fup
= fup
->fu_next
)
547 if (fup
->fu_id
== id
)
553 * Add a new file usage id if it does not already exist.
556 addid(u_long id
, int type
, char *name
, const char *fsname
)
558 struct fileusage
*fup
, **fhp
;
561 if ((fup
= lookup(id
, type
)) != NULL
)
567 if ((fup
= calloc(1, sizeof(*fup
) + len
)) == NULL
)
568 errx(1, "calloc failed");
569 fhp
= &fuhead
[type
][id
& (FUHASH
- 1)];
574 bcopy(name
, fup
->fu_name
, len
+ 1);
576 (void)sprintf(fup
->fu_name
, "%lu", id
);
578 if (aflag
&& fsname
!= NULL
)
579 (void)printf("%s: ", fsname
);
580 printf("unknown %cid: %lu\n",
581 type
== USRQUOTA
? 'u' : 'g', id
);
588 * Special purpose version of ginode used to optimize pass
589 * over all the inodes in numerical order.
591 static ino_t nextino
, lastinum
, lastvalidinum
;
592 static long readcnt
, readpercg
, fullcnt
, inobufsize
, partialcnt
, partialsize
;
593 static caddr_t inodebuf
;
594 #define INOBUFSIZE 56*1024 /* size of buffer to read inodes */
597 getnextinode(ino_t inumber
)
602 static caddr_t nextinop
;
604 if (inumber
!= nextino
++ || inumber
> lastvalidinum
)
605 errx(1, "bad inode number %ju to nextinode",
607 if (inumber
>= lastinum
) {
609 dblk
= fsbtodb(&sblock
, ino_to_fsba(&sblock
, lastinum
));
610 if (readcnt
% readpercg
== 0) {
612 lastinum
+= partialcnt
;
618 * If bread returns an error, it will already have zeroed
619 * out the buffer, so we do not need to do so here.
621 bread(dblk
, inodebuf
, size
);
624 dp
= (union dinode
*)nextinop
;
625 if (sblock
.fs_magic
== FS_UFS1_MAGIC
)
626 nextinop
+= sizeof(struct ufs1_dinode
);
628 nextinop
+= sizeof(struct ufs2_dinode
);
633 * Prepare to scan a set of inodes.
636 setinodebuf(ino_t inum
)
639 if (inum
% sblock
.fs_ipg
!= 0)
640 errx(1, "bad inode number %ju to setinodebuf", (uintmax_t)inum
);
641 lastvalidinum
= inum
+ sblock
.fs_ipg
- 1;
645 if (inodebuf
!= NULL
)
647 inobufsize
= blkroundup(&sblock
, INOBUFSIZE
);
648 fullcnt
= inobufsize
/ ((sblock
.fs_magic
== FS_UFS1_MAGIC
) ?
649 sizeof(struct ufs1_dinode
) : sizeof(struct ufs2_dinode
));
650 readpercg
= sblock
.fs_ipg
/ fullcnt
;
651 partialcnt
= sblock
.fs_ipg
% fullcnt
;
652 partialsize
= partialcnt
* ((sblock
.fs_magic
== FS_UFS1_MAGIC
) ?
653 sizeof(struct ufs1_dinode
) : sizeof(struct ufs2_dinode
));
654 if (partialcnt
!= 0) {
657 partialcnt
= fullcnt
;
658 partialsize
= inobufsize
;
660 if ((inodebuf
= malloc((unsigned)inobufsize
)) == NULL
)
661 errx(1, "cannot allocate space for inode buffer");
665 * Free up data structures used to scan inodes.
671 if (inodebuf
!= NULL
)
677 * Read specified disk blocks.
680 bread(ufs2_daddr_t bno
, char *buf
, long cnt
)
683 if (lseek(fi
, (off_t
)bno
* dev_bsize
, SEEK_SET
) < 0 ||
684 read(fi
, buf
, cnt
) != cnt
)
685 errx(1, "bread failed on block %ld", (long)bno
);
689 * Display updated block and i-node counts.
692 printchanges(const char *fsname
, int type
, struct dqblk
*dp
,
693 struct fileusage
*fup
, u_long id
)
698 (void)printf("%s: ", fsname
);
699 if (fup
->fu_name
[0] == '\0')
700 (void)printf("%-8lu fixed ", id
);
702 (void)printf("%-8s fixed ", fup
->fu_name
);
706 (void)printf("(group):");
710 (void)printf("(user): ");
714 (void)printf("(unknown quota type %d)", type
);
717 if (dp
->dqb_curinodes
!= fup
->fu_curinodes
)
718 (void)printf("\tinodes %lu -> %lu", (u_long
)dp
->dqb_curinodes
,
719 (u_long
)fup
->fu_curinodes
);
720 if (dp
->dqb_curblocks
!= fup
->fu_curblocks
)
721 (void)printf("\tblocks %lu -> %lu",
722 (u_long
)dp
->dqb_curblocks
,
723 (u_long
)fup
->fu_curblocks
);