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