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