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