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