1 /* $OpenBSD: quot.c,v 1.33 2024/04/23 13:34:51 jsg 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/param.h> /* DEV_BSIZE MAXBSIZE */
35 #include <sys/mount.h>
36 #include <sys/time.h>
37 #include <ufs/ufs/dinode.h>
38 #include <ufs/ffs/fs.h>
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <err.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <paths.h>
47 #include <pwd.h>
48 #include <unistd.h>
49
50 /* some flags of what to do: */
51 static char estimate;
52 static char count;
53 static char unused;
54 static void (*func)(int, struct fs *, char *);
55 static int cmpusers(const void *, const void *);
56 void quot(char *, char *);
57 static long blocksize;
58 static char *header;
59 static int headerlen;
60
61 #define SIZE(n) (howmany(((off_t)(n)) * DEV_BSIZE, blocksize))
62
63 #define INOCNT(fs) ((fs)->fs_ipg)
64 #define INOSZ(fs) (((fs)->fs_magic == FS_UFS1_MAGIC ? \
65 sizeof(struct ufs1_dinode) : \
66 sizeof(struct ufs2_dinode)) * INOCNT(fs))
67
68 union dinode {
69 struct ufs1_dinode dp1;
70 struct ufs2_dinode dp2;
71 };
72 #define DIP(fs, dp, field) \
73 (((fs)->fs_magic == FS_UFS1_MAGIC) ? \
74 (dp)->dp1.field : (dp)->dp2.field)
75
76 static union dinode *
get_inode(int fd,struct fs * super,ino_t ino)77 get_inode(int fd, struct fs *super, ino_t ino)
78 {
79 static caddr_t ipbuf;
80 static struct cg *cgp;
81 static ino_t last;
82 static int cg;
83 struct ufs2_dinode *di2;
84
85 if (fd < 0) { /* flush cache */
86 if (ipbuf) {
87 free(ipbuf);
88 ipbuf = NULL;
89 if (super != NULL && super->fs_magic == FS_UFS2_MAGIC) {
90 free(cgp);
91 cgp = NULL;
92 }
93 }
94 return 0;
95 }
96
97 if (!ipbuf || ino < last || ino >= last + INOCNT(super)) {
98 if (super->fs_magic == FS_UFS2_MAGIC &&
99 (!cgp || cg != ino_to_cg(super, ino))) {
100 cg = ino_to_cg(super, ino);
101 if (!cgp && !(cgp = malloc(super->fs_cgsize)))
102 errx(1, "allocate cg");
103 if (pread(fd, cgp, super->fs_cgsize,
104 (off_t)cgtod(super, cg) << super->fs_fshift)
105 != super->fs_cgsize)
106 if (read(fd, cgp, super->fs_cgsize) !=
107 super->fs_cgsize)
108 err(1, "read cg");
109 if (!cg_chkmagic(cgp))
110 errx(1, "cg has bad magic");
111 }
112 if (!ipbuf && !(ipbuf = malloc(INOSZ(super))))
113 err(1, "allocate inodes");
114 last = (ino / INOCNT(super)) * INOCNT(super);
115 if (lseek(fd, (off_t)ino_to_fsba(super, last)
116 << super->fs_fshift, SEEK_SET) < 0 ||
117 read(fd, ipbuf, INOSZ(super)) != INOSZ(super)) {
118 err(1, "read inodes");
119 }
120 }
121
122 if (super->fs_magic == FS_UFS1_MAGIC)
123 return ((union dinode *)
124 &((struct ufs1_dinode *)ipbuf)[ino % INOCNT(super)]);
125 di2 = &((struct ufs2_dinode *)ipbuf)[ino % INOCNT(super)];
126 /* If the inode is unused, it might be unallocated too, so zero it. */
127 if (isclr(cg_inosused(cgp), ino % super->fs_ipg))
128 memset(di2, 0, sizeof(*di2));
129 return ((union dinode *)di2);
130 }
131
132 #define actualblocks(fs, ip) DIP(fs, dp, di_blocks)
133
134 static int
virtualblocks(struct fs * super,union dinode * dp)135 virtualblocks(struct fs *super, union dinode *dp)
136 {
137 off_t nblk, sz;
138
139 sz = DIP(super, dp, di_size);
140
141 if (lblkno(super, sz) >= NDADDR) {
142 nblk = blkroundup(super, sz);
143 sz = lblkno(super, nblk);
144 sz = howmany(sz - NDADDR, NINDIR(super));
145 while (sz > 0) {
146 nblk += sz * super->fs_bsize;
147 /* One block on this level is in the inode itself */
148 sz = howmany(sz - 1, NINDIR(super));
149 }
150 } else
151 nblk = fragroundup(super, sz);
152
153 return nblk / DEV_BSIZE;
154 }
155
156 static int
isfree(struct fs * super,union dinode * dp)157 isfree(struct fs *super, union dinode *dp)
158 {
159 switch (DIP(super, dp, di_mode) & IFMT) {
160 case IFIFO:
161 case IFLNK: /* should check FASTSYMLINK? */
162 case IFDIR:
163 case IFREG:
164 return 0;
165 case IFCHR:
166 case IFBLK:
167 case IFSOCK:
168 case 0:
169 return 1;
170 default:
171 errx(1, "unknown IFMT 0%o", DIP(super, dp, di_mode) & IFMT);
172 }
173 }
174
175 static struct user {
176 uid_t uid;
177 char *name;
178 daddr_t space;
179 long count;
180 daddr_t spc30;
181 daddr_t spc60;
182 daddr_t spc90;
183 } *users;
184 static int nusers;
185
186 static void
inituser(void)187 inituser(void)
188 {
189 int i;
190 struct user *usr;
191
192 if (!nusers) {
193 nusers = 8;
194 if (!(users = calloc(nusers, sizeof(struct user)))) {
195 err(1, "allocate users");
196 }
197 } else {
198 for (usr = users, i = nusers; --i >= 0; usr++) {
199 usr->space = usr->spc30 = usr->spc60 = usr->spc90 = 0;
200 usr->count = 0;
201 }
202 }
203 }
204
205 static void
usrrehash(void)206 usrrehash(void)
207 {
208 int i;
209 struct user *usr, *usrn;
210 struct user *svusr;
211
212 svusr = users;
213 nusers <<= 1;
214 if (!(users = calloc(nusers, sizeof(struct user))))
215 err(1, "allocate users");
216 for (usr = svusr, i = nusers >> 1; --i >= 0; usr++) {
217 for (usrn = users + (usr->uid&(nusers - 1));
218 usrn->name;
219 usrn--) {
220 if (usrn <= users)
221 usrn = users + nusers;
222 }
223 *usrn = *usr;
224 }
225 }
226
227 static struct user *
user(uid_t uid)228 user(uid_t uid)
229 {
230 int i;
231 struct user *usr;
232 const char *name;
233
234 while (1) {
235 for (usr = users + (uid&(nusers - 1)), i = nusers;
236 --i >= 0;
237 usr--) {
238 if (!usr->name) {
239 usr->uid = uid;
240
241 if ((name = user_from_uid(uid, 1)) == NULL)
242 asprintf(&usr->name, "#%u", uid);
243 else
244 usr->name = strdup(name);
245 if (!usr->name)
246 err(1, "allocate users");
247 return usr;
248 } else if (usr->uid == uid)
249 return usr;
250
251 if (usr <= users)
252 usr = users + nusers;
253 }
254 usrrehash();
255 }
256 }
257
258 static int
cmpusers(const void * v1,const void * v2)259 cmpusers(const void *v1, const void *v2)
260 {
261 const struct user *u1 = v1, *u2 = v2;
262
263 return u2->space - u1->space;
264 }
265
266 #define sortusers(users) (qsort((users), nusers, sizeof(struct user), \
267 cmpusers))
268
269 static void
uses(uid_t uid,daddr_t blks,time_t act)270 uses(uid_t uid, daddr_t blks, time_t act)
271 {
272 static time_t today;
273 struct user *usr;
274
275 if (!today)
276 time(&today);
277
278 usr = user(uid);
279 usr->count++;
280 usr->space += blks;
281
282 if (today - act > 90L * 24L * 60L * 60L)
283 usr->spc90 += blks;
284 if (today - act > 60L * 24L * 60L * 60L)
285 usr->spc60 += blks;
286 if (today - act > 30L * 24L * 60L * 60L)
287 usr->spc30 += blks;
288 }
289
290 #define FSZCNT 512
291 struct fsizes {
292 struct fsizes *fsz_next;
293 daddr_t fsz_first, fsz_last;
294 ino_t fsz_count[FSZCNT];
295 daddr_t fsz_sz[FSZCNT];
296 } *fsizes;
297
298 static void
initfsizes(void)299 initfsizes(void)
300 {
301 struct fsizes *fp;
302 int i;
303
304 for (fp = fsizes; fp; fp = fp->fsz_next) {
305 for (i = FSZCNT; --i >= 0;) {
306 fp->fsz_count[i] = 0;
307 fp->fsz_sz[i] = 0;
308 }
309 }
310 }
311
312 static void
dofsizes(int fd,struct fs * super,char * name)313 dofsizes(int fd, struct fs *super, char *name)
314 {
315 ino_t inode, maxino;
316 union dinode *dp;
317 daddr_t sz, ksz;
318 struct fsizes *fp, **fsp;
319 int i;
320
321 maxino = super->fs_ncg * super->fs_ipg - 1;
322 for (inode = 0; inode < maxino; inode++) {
323 errno = 0;
324 if ((dp = get_inode(fd, super, inode))
325 && !isfree(super, dp)
326 ) {
327 sz = estimate ? virtualblocks(super, dp) :
328 actualblocks(super, dp);
329 ksz = SIZE(sz);
330 for (fsp = &fsizes; (fp = *fsp); fsp = &fp->fsz_next) {
331 if (ksz < fp->fsz_last)
332 break;
333 }
334 if (!fp || ksz < fp->fsz_first) {
335 if (!(fp = (struct fsizes *)
336 malloc(sizeof(struct fsizes)))) {
337 err(1, "alloc fsize structure");
338 }
339 fp->fsz_next = *fsp;
340 *fsp = fp;
341 fp->fsz_first = (ksz / FSZCNT) * FSZCNT;
342 fp->fsz_last = fp->fsz_first + FSZCNT;
343 for (i = FSZCNT; --i >= 0;) {
344 fp->fsz_count[i] = 0;
345 fp->fsz_sz[i] = 0;
346 }
347 }
348 fp->fsz_count[ksz % FSZCNT]++;
349 fp->fsz_sz[ksz % FSZCNT] += sz;
350 } else if (errno)
351 err(1, "%s", name);
352 }
353 sz = 0;
354 for (fp = fsizes; fp; fp = fp->fsz_next) {
355 for (i = 0; i < FSZCNT; i++) {
356 if (fp->fsz_count[i])
357 printf("%lld\t%llu\t%lld\n",
358 (long long)fp->fsz_first + i,
359 (unsigned long long)fp->fsz_count[i],
360 SIZE(sz += fp->fsz_sz[i]));
361 }
362 }
363 }
364
365 static void
douser(int fd,struct fs * super,char * name)366 douser(int fd, struct fs *super, char *name)
367 {
368 ino_t inode, maxino;
369 struct user *usr, *usrs;
370 union dinode *dp;
371 int n;
372
373 setpassent(1);
374
375 maxino = super->fs_ncg * super->fs_ipg - 1;
376 for (inode = 0; inode < maxino; inode++) {
377 errno = 0;
378 if ((dp = get_inode(fd,super,inode))
379 && !isfree(super, dp))
380 uses(DIP(super, dp, di_uid),
381 estimate ? virtualblocks(super, dp) :
382 actualblocks(super, dp),
383 DIP(super, dp, di_atime));
384 else if (errno)
385 err(1, "%s", name);
386 }
387 if (!(usrs = calloc(nusers, sizeof(struct user))))
388 err(1, "allocate users");
389 memcpy(usrs, users, nusers * sizeof(struct user));
390 sortusers(usrs);
391 for (usr = usrs, n = nusers; --n >= 0 && usr->count; usr++) {
392 printf("%14lld", SIZE(usr->space));
393 if (count)
394 printf("\t%5ld", usr->count);
395 printf("\t%-8s", usr->name);
396 if (unused)
397 printf("\t%14lld\t%14lld\t%14lld",
398 SIZE(usr->spc30),
399 SIZE(usr->spc60),
400 SIZE(usr->spc90));
401 printf("\n");
402 }
403 free(usrs);
404 }
405
406 static void
donames(int fd,struct fs * super,char * name)407 donames(int fd, struct fs *super, char *name)
408 {
409 int c;
410 unsigned long long inode;
411 ino_t inode1;
412 ino_t maxino;
413 union dinode *dp;
414
415 maxino = super->fs_ncg * super->fs_ipg - 1;
416 /* first skip the name of the filesystem */
417 while ((c = getchar()) != EOF && (c < '0' || c > '9'))
418 while ((c = getchar()) != EOF && c != '\n');
419 ungetc(c, stdin);
420 inode1 = -1;
421 while (scanf("%llu", &inode) == 1) {
422 if (inode > maxino) {
423 fprintf(stderr, "invalid inode %llu\n",
424 (unsigned long long)inode);
425 return;
426 }
427 errno = 0;
428 if ((dp = get_inode(fd, super, inode)) && !isfree(super, dp)) {
429 printf("%s\t", user(DIP(super, dp, di_uid))->name);
430 /* now skip whitespace */
431 while ((c = getchar()) == ' ' || c == '\t');
432 /* and print out the remainder of the input line */
433 while (c != EOF && c != '\n') {
434 putchar(c);
435 c = getchar();
436 }
437 putchar('\n');
438 inode1 = inode;
439 } else {
440 if (errno)
441 err(1, "%s", name);
442 /* skip this line */
443 while ((c = getchar()) != EOF && c != '\n')
444 ;
445 }
446 if (c == EOF)
447 break;
448 }
449 }
450
451 static void
usage(void)452 usage(void)
453 {
454 fprintf(stderr, "usage: quot [-acfhknv] [filesystem ...]\n");
455 exit(1);
456 }
457
458 /*
459 * Possible superblock locations ordered from most to least likely.
460 */
461 static int sblock_try[] = SBLOCKSEARCH;
462 static char superblock[SBLOCKSIZE];
463
464 #define max(a,b) MAX((a),(b))
465 /*
466 * Sanity checks for old file systems.
467 * Stolen from <sys/lib/libsa/ufs.c>
468 */
469 static void
ffs_oldfscompat(struct fs * fs)470 ffs_oldfscompat(struct fs *fs)
471 {
472 int i;
473
474 fs->fs_npsect = max(fs->fs_npsect, fs->fs_nsect); /* XXX */
475 fs->fs_interleave = max(fs->fs_interleave, 1); /* XXX */
476 if (fs->fs_postblformat == FS_42POSTBLFMT) /* XXX */
477 fs->fs_nrpos = 8; /* XXX */
478 if (fs->fs_inodefmt < FS_44INODEFMT) { /* XXX */
479 quad_t sizepb = fs->fs_bsize; /* XXX */
480 /* XXX */
481 fs->fs_maxfilesize = fs->fs_bsize * NDADDR - 1; /* XXX */
482 for (i = 0; i < NIADDR; i++) { /* XXX */
483 sizepb *= NINDIR(fs); /* XXX */
484 fs->fs_maxfilesize += sizepb; /* XXX */
485 } /* XXX */
486 fs->fs_qbmask = ~fs->fs_bmask; /* XXX */
487 fs->fs_qfmask = ~fs->fs_fmask; /* XXX */
488 } /* XXX */
489 }
490
491 void
quot(char * name,char * mp)492 quot(char *name, char *mp)
493 {
494 int i, fd;
495 struct fs *fs;
496
497 get_inode(-1, NULL, 0); /* flush cache */
498 inituser();
499 initfsizes();
500 /*
501 * XXX this is completely broken. Of course you can't read a
502 * directory, well, not anymore. How to fix this, though...
503 */
504 if ((fd = open(name, O_RDONLY)) < 0) {
505 warn("%s", name);
506 return;
507 }
508 for (i = 0; sblock_try[i] != -1; i++) {
509 if (lseek(fd, sblock_try[i], 0) != sblock_try[i]) {
510 close(fd);
511 return;
512 }
513 if (read(fd, superblock, SBLOCKSIZE) != SBLOCKSIZE) {
514 close(fd);
515 return;
516 }
517 fs = (struct fs *)superblock;
518 if ((fs->fs_magic == FS_UFS1_MAGIC ||
519 (fs->fs_magic == FS_UFS2_MAGIC &&
520 fs->fs_sblockloc == sblock_try[i])) &&
521 fs->fs_bsize <= MAXBSIZE &&
522 fs->fs_bsize >= sizeof(struct fs))
523 break;
524 }
525 if (sblock_try[i] == -1) {
526 warnx("%s: not a BSD filesystem", name);
527 close(fd);
528 return;
529 }
530 ffs_oldfscompat(fs);
531 printf("%s:", name);
532 if (mp)
533 printf(" (%s)", mp);
534 putchar('\n');
535 (*func)(fd, fs, name);
536 close(fd);
537 }
538
539 int
main(int argc,char * argv[])540 main(int argc, char *argv[])
541 {
542 int cnt, all, i;
543 char dev[MNAMELEN], *nm, *mountpoint, *cp;
544 struct statfs *mp;
545
546 all = 0;
547 func = douser;
548 header = getbsize(&headerlen, &blocksize);
549 while (--argc > 0 && **++argv == '-') {
550 while (*++*argv) {
551 switch (**argv) {
552 case 'n':
553 func = donames;
554 break;
555 case 'c':
556 func = dofsizes;
557 break;
558 case 'a':
559 all = 1;
560 break;
561 case 'f':
562 count = 1;
563 break;
564 case 'h':
565 estimate = 1;
566 break;
567 case 'k':
568 blocksize = 1024;
569 break;
570 case 'v':
571 unused = 1;
572 break;
573 default:
574 usage();
575 }
576 }
577 }
578
579 if (pledge("stdio rpath getpw", NULL) == -1)
580 err(1, "pledge");
581
582 cnt = getmntinfo(&mp, MNT_NOWAIT);
583 if (all) {
584 for (; --cnt >= 0; mp++) {
585 if (strcmp(mp->f_fstypename, MOUNT_FFS) == 0 ||
586 strcmp(mp->f_fstypename, "ufs") == 0) {
587 if ((nm = strrchr(mp->f_mntfromname, '/'))) {
588 snprintf(dev, sizeof(dev), "%sr%s",
589 _PATH_DEV, nm + 1);
590 nm = dev;
591 } else
592 nm = mp->f_mntfromname;
593 quot(nm, mp->f_mntonname);
594 }
595 }
596 }
597 for (; --argc >= 0; argv++) {
598 mountpoint = NULL;
599 nm = *argv;
600
601 /* Remove trailing slashes from name. */
602 cp = nm + strlen(nm);
603 while (*(--cp) == '/' && cp != nm)
604 *cp = '\0';
605
606 /* Look up the name in the mount table. */
607 for (i = 0; i < cnt; i++) {
608 /* Remove trailing slashes from name. */
609 cp = mp[i].f_mntonname + strlen(mp[i].f_mntonname);
610 while (*(--cp) == '/' && cp != mp[i].f_mntonname)
611 *cp = '\0';
612
613 if ((!strcmp(mp->f_fstypename, MOUNT_FFS) ||
614 !strcmp(mp->f_fstypename, MOUNT_MFS) ||
615 !strcmp(mp->f_fstypename, "ufs")) &&
616 strcmp(nm, mp[i].f_mntonname) == 0) {
617 nm = mp[i].f_mntfromname;
618 mountpoint = mp[i].f_mntonname;
619 break;
620 }
621 }
622
623 /* Make sure we have the raw device... */
624 if (strncmp(nm, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0 &&
625 nm[sizeof(_PATH_DEV) - 1] != 'r') {
626 snprintf(dev, sizeof(dev), "%sr%s", _PATH_DEV,
627 nm + sizeof(_PATH_DEV) - 1);
628 nm = dev;
629 }
630 quot(nm, mountpoint);
631 }
632 exit(0);
633 }
634