xref: /netbsd-src/usr.sbin/quot/quot.c (revision 87ba0e2a319653af510fae24a6d2975d3589735b)
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