1 /*        $NetBSD: quot.c,v 1.35 2022/11/17 06:40:41 chs Exp $        */
2 
3 /*
4  * Copyright (C) 1991, 1994 Wolfgang Solfrank.
5  * Copyright (C) 1991, 1994 TooLs GmbH.
6  * All rights reserved.
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 TooLs GmbH.
19  * 4. The name of TooLs GmbH may not be used to endorse or promote products
20  *    derived from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR
23  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25  * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
28  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 #include <sys/cdefs.h>
35 #ifndef lint
36 __RCSID("$NetBSD: quot.c,v 1.35 2022/11/17 06:40:41 chs Exp $");
37 #endif /* not lint */
38 
39 #include <sys/param.h>
40 #include <sys/mount.h>
41 #include <sys/time.h>
42 #include <ufs/ufs/dinode.h>
43 #include <ufs/ffs/fs.h>
44 
45 #include <err.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <pwd.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53 
54 /* some flags of what to do: */
55 static char estimate;
56 static char count;
57 static char unused;
58 static void (*func)(int, struct fs *, const char *);
59 static long blocksize;
60 static char *header;
61 
62 /*
63  * Original BSD quot doesn't round to number of frags/blocks,
64  * doesn't account for indirection blocks and gets it totally
65  * wrong if the     size is a multiple of the blocksize.
66  * The new code always counts the number of DEV_BSIZE byte blocks
67  * instead of the number of kilobytes and converts them     to
68  * kByte when done (on request).
69  */
70 #ifdef    COMPAT
71 #define   SIZE(n)   ((long long)(n))
72 #else
73 #define   SIZE(n)   howmany((long long)(n) * DEV_BSIZE, (long long)blocksize)
74 #endif
75 
76 #define   INOCNT(fs)          ((fs)->fs_ipg)
77 #define INOSZ(fs) \
78           (((fs)->fs_magic == FS_UFS1_MAGIC ? sizeof(struct ufs1_dinode) : \
79           sizeof(struct ufs2_dinode)) * INOCNT(fs))
80 
81 union dinode {
82           struct ufs1_dinode dp1;
83           struct ufs2_dinode dp2;
84 };
85 #define       DIP(fs, dp, field) \
86           (((fs)->fs_magic == FS_UFS1_MAGIC) ? \
87           (dp)->dp1.di_##field : (dp)->dp2.di_##field)
88 
89 
90 static    int                 cmpusers(const void *, const void *);
91 static    void                dofsizes(int, struct fs *, const char *);
92 static    void                donames(int, struct fs *, const char *);
93 static    void                douser(int, struct fs *, const char *);
94 static    union dinode  *get_inode(int, struct fs *, ino_t);
95 static    void                ffs_oldfscompat(struct fs *);
96 static    void                initfsizes(void);
97 static    void                inituser(void);
98 static    int                 isfree(struct fs *, union dinode *);
99 static    void                quot(const char *, const char *);
100 static    void                usage(void) __attribute__((__noreturn__));
101 static    struct user    *user(uid_t);
102 static    void                uses(uid_t, daddr_t, time_t);
103 static    void                usrrehash(void);
104 static    int                 virtualblocks(struct fs *, union dinode *);
105 
106 
107 static union dinode *
get_inode(int fd,struct fs * super,ino_t ino)108 get_inode(int fd, struct fs *super, ino_t ino)
109 {
110           static char *ipbuf;
111           static ino_t last;
112 
113           if (fd < 0) {                 /* flush cache */
114                     if (ipbuf) {
115                               free(ipbuf);
116                               ipbuf = NULL;
117                     }
118                     return 0;
119           }
120 
121           if (!ipbuf || ino < last || ino >= last + INOCNT(super)) {
122                     if (!ipbuf
123                         && !(ipbuf = malloc(INOSZ(super))))
124                               errx(1, "allocate inodes");
125                     last = (ino / INOCNT(super)) * INOCNT(super);
126                     if (lseek(fd,
127                         (off_t)ino_to_fsba(super, last) << super->fs_fshift,
128                         0) < 0 ||
129                         read(fd, ipbuf, INOSZ(super)) != (ssize_t)INOSZ(super))
130                               errx(1, "read inodes");
131           }
132 
133           if (super->fs_magic == FS_UFS1_MAGIC)
134                     return ((union dinode *)
135                         &((struct ufs1_dinode *)ipbuf)[ino % INOCNT(super)]);
136           return ((union dinode *)
137               &((struct ufs2_dinode *)ipbuf)[ino % INOCNT(super)]);
138 }
139 
140 #ifdef    COMPAT
141 #define   actualblocks(fs, dp)          (int)(DIP(fs, dp, blocks) / 2)
142 #else
143 #define   actualblocks(fs, dp)          (int)(DIP(fs, dp, blocks))
144 #endif
145 
146 static int
virtualblocks(struct fs * super,union dinode * dp)147 virtualblocks(struct fs *super, union dinode *dp)
148 {
149           off_t nblk, sz;
150 
151           sz = DIP(super, dp, size);
152 #ifdef    COMPAT
153           if (ffs_lblkno(super, sz) >= UFS_NDADDR) {
154                     nblk = ffs_blkroundup(super, sz);
155                     if (sz == nblk)
156                               nblk += super->fs_bsize;
157           }
158 
159           return sz / 1024;
160 #else     /* COMPAT */
161 
162           if (ffs_lblkno(super, sz) >= UFS_NDADDR) {
163                     nblk = ffs_blkroundup(super, sz);
164                     sz = ffs_lblkno(super, nblk);
165                     sz = howmany(sz - UFS_NDADDR, FFS_NINDIR(super));
166                     while (sz > 0) {
167                               nblk += sz * super->fs_bsize;
168                               /* One block on this level is in the inode itself */
169                               sz = howmany(sz - 1, FFS_NINDIR(super));
170                     }
171           } else
172                     nblk = ffs_fragroundup(super, sz);
173 
174           return nblk / DEV_BSIZE;
175 #endif    /* COMPAT */
176 }
177 
178 static int
isfree(fs,dp)179 isfree(fs, dp)
180           struct fs *fs;
181           union dinode *dp;
182 {
183 #ifdef    COMPAT
184           return (DIP(fs, dp, mode) & IFMT) == 0;
185 #else     /* COMPAT */
186           switch (DIP(fs, dp, mode) & IFMT) {
187           case IFIFO:
188           case IFLNK:                   /* should check FASTSYMLINK? */
189           case IFDIR:
190           case IFREG:
191                     return 0;
192           default:
193                     return 1;
194           }
195 #endif
196 }
197 
198 static struct user {
199           uid_t uid;
200           char *name;
201           daddr_t space;
202           long count;
203           daddr_t spc30;
204           daddr_t spc60;
205           daddr_t spc90;
206 } *users;
207 static int nusers;
208 
209 static void
inituser(void)210 inituser(void)
211 {
212           int i;
213           struct user *usr;
214 
215           if (!nusers) {
216                     nusers = 8;
217                     if (!(users = calloc(nusers, sizeof(*users))))
218                               err(1, "allocate users");
219           } else {
220                     for (usr = users, i = nusers; --i >= 0; usr++) {
221                               usr->space = usr->spc30 = usr->spc60 = usr->spc90 = 0;
222                               usr->count = 0;
223                     }
224           }
225 }
226 
227 static void
usrrehash(void)228 usrrehash(void)
229 {
230           int i;
231           struct user *usr, *usrn;
232           struct user *svusr;
233 
234           svusr = users;
235           nusers <<= 1;
236           if (!(users = calloc(nusers, sizeof(*users))))
237                     err(1, "allocate users");
238           for (usr = svusr, i = nusers >> 1; --i >= 0; usr++) {
239                     for (usrn = users + (usr->uid&(nusers - 1));
240                          usrn->name;
241                          usrn--) {
242                               if (usrn <= users)
243                                         usrn = users + nusers;
244                     }
245                     *usrn = *usr;
246           }
247 }
248 
249 static struct user *
user(uid_t uid)250 user(uid_t uid)
251 {
252           struct user *usr;
253           int i;
254           struct passwd *pwd;
255 
256           for (;;) {
257                     for (usr = users + (uid & (nusers - 1)), i = nusers;
258                          --i >= 0;
259                          usr--) {
260                               if (!usr->name) {
261                                         usr->uid = uid;
262 
263                                         if (!(pwd = getpwuid(uid)))
264                                                   asprintf(&usr->name, "#%u", uid);
265                                         else
266                                                   asprintf(&usr->name, "%s",
267                                                       pwd->pw_name);
268                                         if (!usr->name)
269                                                   errx(1, "allocate users");
270                                         return usr;
271                               } else if (usr->uid == uid)
272                                         return usr;
273 
274                               if (usr <= users)
275                                         usr = users + nusers;
276                     }
277                     usrrehash();
278           }
279 }
280 
281 static int
cmpusers(u1,u2)282 cmpusers(u1, u2)
283           const void *u1, *u2;
284 {
285           return ((const struct user *)u2)->space - ((const struct user *)u1)->space;
286 }
287 
288 #define   sortusers(users)    (qsort((users), nusers, sizeof(struct user), \
289                                                cmpusers))
290 
291 static void
uses(uid,blks,act)292 uses(uid, blks, act)
293           uid_t uid;
294           daddr_t blks;
295           time_t act;
296 {
297           static time_t today;
298           struct user *usr;
299 
300           if (!today)
301                     time(&today);
302 
303           usr = user(uid);
304           usr->count++;
305           usr->space += blks;
306 
307           if (today - act > 90L * 24L * 60L * 60L)
308                     usr->spc90 += blks;
309           if (today - act > 60L * 24L * 60L * 60L)
310                     usr->spc60 += blks;
311           if (today - act > 30L * 24L * 60L * 60L)
312                     usr->spc30 += blks;
313 }
314 
315 #ifdef    COMPAT
316 #define   FSZCNT    500
317 #else
318 #define   FSZCNT    512
319 #endif
320 struct fsizes {
321           struct fsizes *fsz_next;
322           daddr_t fsz_first, fsz_last;
323           ino_t fsz_count[FSZCNT];
324           daddr_t fsz_sz[FSZCNT];
325 } *fsizes;
326 
327 static void
initfsizes()328 initfsizes()
329 {
330           struct fsizes *fp;
331           int i;
332 
333           for (fp = fsizes; fp; fp = fp->fsz_next) {
334                     for (i = FSZCNT; --i >= 0;) {
335                               fp->fsz_count[i] = 0;
336                               fp->fsz_sz[i] = 0;
337                     }
338           }
339 }
340 
341 static void
dofsizes(int fd,struct fs * super,const char * name)342 dofsizes(int fd, struct fs *super, const char *name)
343 {
344           ino_t inode, maxino;
345           union dinode *dp;
346           daddr_t sz, ksz;
347           struct fsizes *fp, **fsp;
348           int i;
349 
350           maxino = super->fs_ncg * super->fs_ipg - 1;
351 #ifdef    COMPAT
352           if (!(fsizes = malloc(sizeof(*fsizes))))
353                     err(1, "alloc fsize structure");
354 #endif    /* COMPAT */
355           for (inode = 0; inode < maxino; inode++) {
356                     errno = 0;
357                     if ((dp = get_inode(fd, super, inode))
358 #ifdef    COMPAT
359                         && ((DIP(super, dp, mode) & IFMT) == IFREG
360                               || (DIP(dp, mode) & IFMT) == IFDIR)
361 #else     /* COMPAT */
362                         && !isfree(super, dp)
363 #endif    /* COMPAT */
364                         ) {
365                               sz = estimate ? virtualblocks(super, dp) :
366                                   actualblocks(super, dp);
367 #ifdef    COMPAT
368                               if (sz >= FSZCNT) {
369                                         fsizes->fsz_count[FSZCNT-1]++;
370                                         fsizes->fsz_sz[FSZCNT-1] += sz;
371                               } else {
372                                         fsizes->fsz_count[sz]++;
373                                         fsizes->fsz_sz[sz] += sz;
374                               }
375 #else     /* COMPAT */
376                               ksz = SIZE(sz);
377                               for (fsp = &fsizes; (fp = *fsp) != NULL;
378                                   fsp = &fp->fsz_next) {
379                                         if (ksz < fp->fsz_last)
380                                                   break;
381                               }
382                               if (!fp || ksz < fp->fsz_first) {
383                                         if (!(fp = malloc(sizeof(*fp))))
384                                                   err(1, "alloc fsize structure");
385                                         fp->fsz_next = *fsp;
386                                         *fsp = fp;
387                                         fp->fsz_first = (ksz / FSZCNT) * FSZCNT;
388                                         fp->fsz_last = fp->fsz_first + FSZCNT;
389                                         for (i = FSZCNT; --i >= 0;) {
390                                                   fp->fsz_count[i] = 0;
391                                                   fp->fsz_sz[i] = 0;
392                                         }
393                               }
394                               fp->fsz_count[ksz % FSZCNT]++;
395                               fp->fsz_sz[ksz % FSZCNT] += sz;
396 #endif    /* COMPAT */
397                     } else if (errno)
398                               errx(1, "%s", name);
399           }
400           sz = 0;
401           for (fp = fsizes; fp; fp = fp->fsz_next) {
402                     for (i = 0; i < FSZCNT; i++) {
403                               if (fp->fsz_count[i])
404                                         printf("%ld\t%ld\t%lld\n",
405                                             (long)(fp->fsz_first + i),
406                                             (long)fp->fsz_count[i],
407                                             SIZE(sz += fp->fsz_sz[i]));
408                     }
409           }
410 }
411 
412 static void
douser(int fd,struct fs * super,const char * name)413 douser(int fd, struct fs *super, const char *name)
414 {
415           ino_t inode, maxino;
416           struct user *usr, *usrs;
417           union dinode *dp;
418           int n;
419 
420           maxino = super->fs_ncg * super->fs_ipg - 1;
421           for (inode = 0; inode < maxino; inode++) {
422                     errno = 0;
423                     if ((dp = get_inode(fd, super, inode))
424                         && !isfree(super, dp))
425                               uses(DIP(super, dp, uid),
426                                   estimate ? virtualblocks(super, dp) :
427                                   actualblocks(super, dp), DIP(super, dp, atime));
428                     else if (errno)
429                               errx(1, "%s", name);
430           }
431           if (!(usrs = calloc(nusers, sizeof(*usrs))))
432                     errx(1, "allocate users");
433           memmove(usrs, users, nusers * sizeof(*usrs));
434           sortusers(usrs);
435           for (usr = usrs, n = nusers; --n >= 0 && usr->count; usr++) {
436                     printf("%5lld", SIZE(usr->space));
437                     if (count)
438                               printf("\t%5ld", usr->count);
439                     printf("\t%-8s", usr->name);
440                     if (unused)
441                               printf("\t%5lld\t%5lld\t%5lld",
442                                   SIZE(usr->spc30), SIZE(usr->spc60),
443                                   SIZE(usr->spc90));
444                     printf("\n");
445           }
446           free(usrs);
447 }
448 
449 static void
donames(int fd,struct fs * super,const char * name)450 donames(int fd, struct fs *super, const char *name)
451 {
452           int c;
453           ino_t inode;
454 #ifdef COMPAT
455           ino_t inode1 = -1;
456 #endif
457           ino_t maxino;
458           union dinode *dp;
459 
460           maxino = super->fs_ncg * super->fs_ipg - 1;
461           /* first skip the name of the filesystem */
462           while ((c = getchar()) != EOF && (c < '0' || c > '9'))
463                     while ((c = getchar()) != EOF && c != '\n');
464           ungetc(c, stdin);
465           while (scanf("%" SCNu64, &inode) == 1) {
466                     if (inode > maxino) {
467 #ifndef   COMPAT
468                               warnx("invalid inode %" PRIu64, inode);
469 #endif
470                               return;
471                     }
472 #ifdef    COMPAT
473                     if (inode < inode1)
474                               continue;
475 #endif
476                     errno = 0;
477                     if ((dp = get_inode(fd, super, inode))
478                         && !isfree(super, dp)) {
479                               printf("%s\t", user(DIP(super, dp, uid))->name);
480                               /* now skip whitespace */
481                               while ((c = getchar()) == ' ' || c == '\t');
482                               /* and print out the remainder of the input line */
483                               while (c != EOF && c != '\n') {
484                                         putchar(c);
485                                         c = getchar();
486                               }
487                               putchar('\n');
488 #ifdef COMPAT
489                               inode1 = inode;
490 #endif
491                     } else {
492                               if (errno)
493                                         errx(1, "%s", name);
494                               /* skip this line */
495                               while ((c = getchar()) != EOF && c != '\n')
496                                         continue;
497                     }
498                     if (c == EOF)
499                               break;
500           }
501 }
502 
503 static void
usage(void)504 usage(void)
505 {
506           const char *p = getprogname();
507 #ifdef    COMPAT
508           fprintf(stderr, "Usage: %s [-nfcvha] [<filesystem> ...]\n", p);
509 #else     /* COMPAT */
510           fprintf(stderr, "Usage: %s [ -acfhknv ] [<filesystem> ... ]\n", p);
511 #endif    /* COMPAT */
512           exit(1);
513 }
514 
515 /*
516  * Sanity checks for old file systems.
517  * Stolen from <sys/lib/libsa/ufs.c>
518  */
519 static void
ffs_oldfscompat(struct fs * fs)520 ffs_oldfscompat(struct fs *fs)
521 {
522           int i;
523 
524           if (fs->fs_magic == FS_UFS1_MAGIC &&
525               fs->fs_old_inodefmt < FS_44INODEFMT) {
526                     quad_t sizepb = fs->fs_bsize;
527 
528                     fs->fs_maxfilesize = fs->fs_bsize * UFS_NDADDR - 1;
529                     for (i = 0; i < UFS_NIADDR; i++) {
530                               sizepb *= FFS_NINDIR(fs);
531                               fs->fs_maxfilesize += sizepb;
532                     }
533                     fs->fs_qbmask = ~fs->fs_bmask;
534                     fs->fs_qfmask = ~fs->fs_fmask;
535           }
536 }
537 
538 /*
539  * Possible superblock locations ordered from most to least likely.
540  */
541 static int sblock_try[] = SBLOCKSEARCH;
542 static char superblock[SBLOCKSIZE];
543 
544 
545 static void
quot(const char * name,const char * mp)546 quot(const char *name, const char *mp)
547 {
548           int fd, i;
549           struct fs *fs;
550           int sbloc;
551 
552           get_inode(-1, 0, 0);                    /* flush cache */
553           inituser();
554           initfsizes();
555           if ((fd = open(name, 0)) < 0) {
556                     warn("%s", name);
557                     return;
558           }
559 
560           for (i = 0; ; i++) {
561                     sbloc = sblock_try[i];
562                     if (sbloc == -1) {
563                               warnx("%s: not a BSD filesystem", name);
564                               close(fd);
565                               return;
566                     }
567                     if (pread(fd, superblock, SBLOCKSIZE, sbloc) != SBLOCKSIZE)
568                               continue;
569                     fs = (struct fs *)superblock;
570 
571                     if (fs->fs_magic != FS_UFS1_MAGIC &&
572                         fs->fs_magic != FS_UFS2_MAGIC &&
573                         fs->fs_magic != FS_UFS2EA_MAGIC)
574                               continue;
575 
576                     if (fs->fs_magic == FS_UFS2_MAGIC
577                         || fs->fs_magic == FS_UFS2EA_MAGIC
578                         || fs->fs_old_flags & FS_FLAGS_UPDATED) {
579                               /* Not the main superblock */
580                               if (fs->fs_sblockloc != sbloc)
581                                         continue;
582                     } else {
583                               /* might be a first alt. id blocksize 64k */
584                               if (sbloc == SBLOCK_UFS2)
585                                         continue;
586                     }
587 
588                     if (fs->fs_bsize > MAXBSIZE ||
589                         (size_t)fs->fs_bsize < sizeof(struct fs))
590                               continue;
591                     break;
592           }
593 
594           ffs_oldfscompat((struct fs *)superblock);
595           printf("%s:", name);
596           if (mp)
597                     printf(" (%s)", mp);
598           putchar('\n');
599           (*func)(fd, fs, name);
600           close(fd);
601 }
602 
603 int
main(int argc,char ** argv)604 main(int argc, char **argv)
605 {
606           char all = 0;
607           struct statvfs *mp;
608           char dev[MNAMELEN + 1];
609           char *nm;
610           int cnt;
611 
612           func = douser;
613 #ifndef   COMPAT
614           header = getbsize(NULL, &blocksize);
615 #endif
616           while (--argc > 0 && **++argv == '-') {
617                     while (*++*argv) {
618                               switch (**argv) {
619                               case 'n':
620                                         func = donames;
621                                         break;
622                               case 'c':
623                                         func = dofsizes;
624                                         break;
625                               case 'a':
626                                         all = 1;
627                                         break;
628                               case 'f':
629                                         count = 1;
630                                         break;
631                               case 'h':
632                                         estimate = 1;
633                                         break;
634 #ifndef   COMPAT
635                               case 'k':
636                                         blocksize = 1024;
637                                         break;
638 #endif    /* COMPAT */
639                               case 'v':
640                                         unused = 1;
641                                         break;
642                               default:
643                                         usage();
644                               }
645                     }
646           }
647           if (all) {
648                     cnt = getmntinfo(&mp, MNT_NOWAIT);
649                     for (; --cnt >= 0; mp++) {
650                               if (!strncmp(mp->f_fstypename, MOUNT_FFS,
651                                   sizeof(mp->f_fstypename))) {
652                                         if ((nm =
653                                             strrchr(mp->f_mntfromname, '/')) != NULL) {
654                                                   snprintf(dev, sizeof(dev), "/dev/r%s",
655                                                       nm + 1);
656                                                   nm = dev;
657                                         } else
658                                                   nm = mp->f_mntfromname;
659                                         quot(nm, mp->f_mntonname);
660                               }
661                     }
662           }
663           while (--argc >= 0)
664                     quot(*argv++, 0);
665           return 0;
666 }
667