xref: /netbsd/usr.sbin/quot/quot.c (revision 6550d01e)
1 /*	$NetBSD: quot.c,v 1.28 2009/04/18 08:17:23 lukem 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.28 2009/04/18 08:17:23 lukem 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) __P((int, struct fs *, 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 __P((const void *, const void *));
91 static	void		dofsizes __P((int, struct fs *, char *));
92 static	void		donames __P((int, struct fs *, char *));
93 static	void		douser __P((int, struct fs *, char *));
94 static	union dinode  *get_inode __P((int, struct fs*, ino_t));
95 static	void		ffs_oldfscompat __P((struct fs *));
96 static	void		initfsizes __P((void));
97 static	void		inituser __P((void));
98 static	int		isfree __P((struct fs *, union dinode *));
99 	int		main __P((int, char **));
100 	void		quot __P((char *, char *));
101 static	void		usage __P((void));
102 static	struct user    *user __P((uid_t));
103 static	void		uses __P((uid_t, daddr_t, time_t));
104 static	void		usrrehash __P((void));
105 static	int		virtualblocks __P((struct fs *, union dinode *));
106 
107 
108 static union dinode *
109 get_inode(fd, super, ino)
110 	int fd;
111 	struct fs *super;
112 	ino_t ino;
113 {
114 	static char *ipbuf;
115 	static ino_t last;
116 
117 	if (fd < 0) {		/* flush cache */
118 		if (ipbuf) {
119 			free(ipbuf);
120 			ipbuf = NULL;
121 		}
122 		return 0;
123 	}
124 
125 	if (!ipbuf || ino < last || ino >= last + INOCNT(super)) {
126 		if (!ipbuf
127 		    && !(ipbuf = malloc(INOSZ(super))))
128 			errx(1, "allocate inodes");
129 		last = (ino / INOCNT(super)) * INOCNT(super);
130 		if (lseek(fd,
131 		    (off_t)ino_to_fsba(super, last) << super->fs_fshift,
132 		    0) < 0 ||
133 		    read(fd, ipbuf, INOSZ(super)) != INOSZ(super))
134 			errx(1, "read inodes");
135 	}
136 
137 	if (super->fs_magic == FS_UFS1_MAGIC)
138 		return ((union dinode *)
139 		    &((struct ufs1_dinode *)ipbuf)[ino % INOCNT(super)]);
140 	return ((union dinode *)
141 	    &((struct ufs2_dinode *)ipbuf)[ino % INOCNT(super)]);
142 }
143 
144 #ifdef	COMPAT
145 #define	actualblocks(fs, dp)	(DIP(fs, dp, blocks) / 2)
146 #else
147 #define	actualblocks(fs, dp)	(DIP(fs, dp, blocks))
148 #endif
149 
150 static int
151 virtualblocks(super, dp)
152 	struct fs *super;
153 	union dinode *dp;
154 {
155 	off_t nblk, sz;
156 
157 	sz = DIP(super, dp, size);
158 #ifdef	COMPAT
159 	if (lblkno(super, sz) >= NDADDR) {
160 		nblk = blkroundup(super, sz);
161 		if (sz == nblk)
162 			nblk += super->fs_bsize;
163 	}
164 
165 	return sz / 1024;
166 #else	/* COMPAT */
167 
168 	if (lblkno(super, sz) >= NDADDR) {
169 		nblk = blkroundup(super, sz);
170 		sz = lblkno(super, nblk);
171 		sz = howmany(sz - NDADDR, NINDIR(super));
172 		while (sz > 0) {
173 			nblk += sz * super->fs_bsize;
174 			/* One block on this level is in the inode itself */
175 			sz = howmany(sz - 1, NINDIR(super));
176 		}
177 	} else
178 		nblk = fragroundup(super, sz);
179 
180 	return nblk / DEV_BSIZE;
181 #endif	/* COMPAT */
182 }
183 
184 static int
185 isfree(fs, dp)
186 	struct fs *fs;
187 	union dinode *dp;
188 {
189 #ifdef	COMPAT
190 	return (DIP(fs, dp, mode) & IFMT) == 0;
191 #else	/* COMPAT */
192 	switch (DIP(fs, dp, mode) & IFMT) {
193 	case IFIFO:
194 	case IFLNK:		/* should check FASTSYMLINK? */
195 	case IFDIR:
196 	case IFREG:
197 		return 0;
198 	default:
199 		return 1;
200 	}
201 #endif
202 }
203 
204 static struct user {
205 	uid_t uid;
206 	char *name;
207 	daddr_t space;
208 	long count;
209 	daddr_t spc30;
210 	daddr_t spc60;
211 	daddr_t spc90;
212 } *users;
213 static int nusers;
214 
215 static void
216 inituser()
217 {
218 	int i;
219 	struct user *usr;
220 
221 	if (!nusers) {
222 		nusers = 8;
223 		if (!(users =
224 		    (struct user *)calloc(nusers, sizeof(struct user))))
225 			errx(1, "allocate users");
226 	} else {
227 		for (usr = users, i = nusers; --i >= 0; usr++) {
228 			usr->space = usr->spc30 = usr->spc60 = usr->spc90 = 0;
229 			usr->count = 0;
230 		}
231 	}
232 }
233 
234 static void
235 usrrehash()
236 {
237 	int i;
238 	struct user *usr, *usrn;
239 	struct user *svusr;
240 
241 	svusr = users;
242 	nusers <<= 1;
243 	if (!(users = (struct user *)calloc(nusers, sizeof(struct user))))
244 		errx(1, "allocate users");
245 	for (usr = svusr, i = nusers >> 1; --i >= 0; usr++) {
246 		for (usrn = users + (usr->uid&(nusers - 1));
247 		     usrn->name;
248 		     usrn--) {
249 			if (usrn <= users)
250 				usrn = users + nusers;
251 		}
252 		*usrn = *usr;
253 	}
254 }
255 
256 static struct user *
257 user(uid)
258 	uid_t uid;
259 {
260 	struct user *usr;
261 	int i;
262 	struct passwd *pwd;
263 
264 	while (1) {
265 		for (usr = users + (uid&(nusers - 1)), i = nusers;
266 		     --i >= 0;
267 		     usr--) {
268 			if (!usr->name) {
269 				usr->uid = uid;
270 
271 				if (!(pwd = getpwuid(uid))) {
272 					if ((usr->name =
273 					    (char *)malloc(7)) != NULL)
274 						sprintf(usr->name, "#%d", uid);
275 				} else {
276 					if ((usr->name =
277 					    (char *)malloc(
278 						strlen(pwd->pw_name) + 1))
279 					    != NULL)
280 						strcpy(usr->name, pwd->pw_name);
281 				}
282 				if (!usr->name)
283 					errx(1, "allocate users");
284 				return usr;
285 			} else if (usr->uid == uid)
286 				return usr;
287 
288 			if (usr <= users)
289 				usr = users + nusers;
290 		}
291 		usrrehash();
292 	}
293 }
294 
295 static int
296 cmpusers(u1, u2)
297 	const void *u1, *u2;
298 {
299 	return ((const struct user *)u2)->space - ((const struct user *)u1)->space;
300 }
301 
302 #define	sortusers(users)	(qsort((users), nusers, sizeof(struct user), \
303 				       cmpusers))
304 
305 static void
306 uses(uid, blks, act)
307 	uid_t uid;
308 	daddr_t blks;
309 	time_t act;
310 {
311 	static time_t today;
312 	struct user *usr;
313 
314 	if (!today)
315 		time(&today);
316 
317 	usr = user(uid);
318 	usr->count++;
319 	usr->space += blks;
320 
321 	if (today - act > 90L * 24L * 60L * 60L)
322 		usr->spc90 += blks;
323 	if (today - act > 60L * 24L * 60L * 60L)
324 		usr->spc60 += blks;
325 	if (today - act > 30L * 24L * 60L * 60L)
326 		usr->spc30 += blks;
327 }
328 
329 #ifdef	COMPAT
330 #define	FSZCNT	500
331 #else
332 #define	FSZCNT	512
333 #endif
334 struct fsizes {
335 	struct fsizes *fsz_next;
336 	daddr_t fsz_first, fsz_last;
337 	ino_t fsz_count[FSZCNT];
338 	daddr_t fsz_sz[FSZCNT];
339 } *fsizes;
340 
341 static void
342 initfsizes()
343 {
344 	struct fsizes *fp;
345 	int i;
346 
347 	for (fp = fsizes; fp; fp = fp->fsz_next) {
348 		for (i = FSZCNT; --i >= 0;) {
349 			fp->fsz_count[i] = 0;
350 			fp->fsz_sz[i] = 0;
351 		}
352 	}
353 }
354 
355 static void
356 dofsizes(fd, super, name)
357 	int fd;
358 	struct fs *super;
359 	char *name;
360 {
361 	ino_t inode, maxino;
362 	union dinode *dp;
363 	daddr_t sz, ksz;
364 	struct fsizes *fp, **fsp;
365 	int i;
366 
367 	maxino = super->fs_ncg * super->fs_ipg - 1;
368 #ifdef	COMPAT
369 	if (!(fsizes = (struct fsizes *)malloc(sizeof(struct fsizes))))
370 		errx(1, "alloc fsize structure");
371 #endif	/* COMPAT */
372 	for (inode = 0; inode < maxino; inode++) {
373 		errno = 0;
374 		if ((dp = get_inode(fd, super, inode))
375 #ifdef	COMPAT
376 		    && ((DIP(super, dp, mode) & IFMT) == IFREG
377 			|| (DIP(dp, mode) & IFMT) == IFDIR)
378 #else	/* COMPAT */
379 		    && !isfree(super, dp)
380 #endif	/* COMPAT */
381 		    ) {
382 			sz = estimate ? virtualblocks(super, dp) :
383 			    actualblocks(super, dp);
384 #ifdef	COMPAT
385 			if (sz >= FSZCNT) {
386 				fsizes->fsz_count[FSZCNT-1]++;
387 				fsizes->fsz_sz[FSZCNT-1] += sz;
388 			} else {
389 				fsizes->fsz_count[sz]++;
390 				fsizes->fsz_sz[sz] += sz;
391 			}
392 #else	/* COMPAT */
393 			ksz = SIZE(sz);
394 			for (fsp = &fsizes; (fp = *fsp) != NULL;
395 			    fsp = &fp->fsz_next) {
396 				if (ksz < fp->fsz_last)
397 					break;
398 			}
399 			if (!fp || ksz < fp->fsz_first) {
400 				if (!(fp = (struct fsizes *)
401 				      malloc(sizeof(struct fsizes))))
402 					errx(1, "alloc fsize structure");
403 				fp->fsz_next = *fsp;
404 				*fsp = fp;
405 				fp->fsz_first = (ksz / FSZCNT) * FSZCNT;
406 				fp->fsz_last = fp->fsz_first + FSZCNT;
407 				for (i = FSZCNT; --i >= 0;) {
408 					fp->fsz_count[i] = 0;
409 					fp->fsz_sz[i] = 0;
410 				}
411 			}
412 			fp->fsz_count[ksz % FSZCNT]++;
413 			fp->fsz_sz[ksz % FSZCNT] += sz;
414 #endif	/* COMPAT */
415 		} else if (errno)
416 			errx(1, "%s", name);
417 	}
418 	sz = 0;
419 	for (fp = fsizes; fp; fp = fp->fsz_next) {
420 		for (i = 0; i < FSZCNT; i++) {
421 			if (fp->fsz_count[i])
422 				printf("%ld\t%ld\t%lld\n",
423 				    (long)(fp->fsz_first + i),
424 				    (long)fp->fsz_count[i],
425 				    SIZE(sz += fp->fsz_sz[i]));
426 		}
427 	}
428 }
429 
430 static void
431 douser(fd, super, name)
432 	int fd;
433 	struct fs *super;
434 	char *name;
435 {
436 	ino_t inode, maxino;
437 	struct user *usr, *usrs;
438 	union dinode *dp;
439 	int n;
440 
441 	maxino = super->fs_ncg * super->fs_ipg - 1;
442 	for (inode = 0; inode < maxino; inode++) {
443 		errno = 0;
444 		if ((dp = get_inode(fd, super, inode))
445 		    && !isfree(super, dp))
446 			uses(DIP(super, dp, uid),
447 			    estimate ? virtualblocks(super, dp) :
448 			    actualblocks(super, dp), DIP(super, dp, atime));
449 		else if (errno)
450 			errx(1, "%s", name);
451 	}
452 	if (!(usrs = (struct user *)malloc(nusers * sizeof(struct user))))
453 		errx(1, "allocate users");
454 	memmove(usrs, users, nusers * sizeof(struct user));
455 	sortusers(usrs);
456 	for (usr = usrs, n = nusers; --n >= 0 && usr->count; usr++) {
457 		printf("%5lld", SIZE(usr->space));
458 		if (count)
459 			printf("\t%5ld", usr->count);
460 		printf("\t%-8s", usr->name);
461 		if (unused)
462 			printf("\t%5lld\t%5lld\t%5lld",
463 			    SIZE(usr->spc30), SIZE(usr->spc60),
464 			    SIZE(usr->spc90));
465 		printf("\n");
466 	}
467 	free(usrs);
468 }
469 
470 static void
471 donames(fd, super, name)
472 	int fd;
473 	struct fs *super;
474 	char *name;
475 {
476 	int c;
477 	ino_t inode, inode1;
478 	ino_t maxino;
479 	union dinode *dp;
480 
481 	maxino = super->fs_ncg * super->fs_ipg - 1;
482 	/* first skip the name of the filesystem */
483 	while ((c = getchar()) != EOF && (c < '0' || c > '9'))
484 		while ((c = getchar()) != EOF && c != '\n');
485 	ungetc(c, stdin);
486 	inode1 = -1;
487 	while (scanf("%" SCNu64, &inode) == 1) {
488 		if (inode > maxino) {
489 #ifndef	COMPAT
490 			warnx("invalid inode %" PRIu64, inode);
491 #endif
492 			return;
493 		}
494 #ifdef	COMPAT
495 		if (inode < inode1)
496 			continue;
497 #endif
498 		errno = 0;
499 		if ((dp = get_inode(fd, super, inode))
500 		    && !isfree(super, dp)) {
501 			printf("%s\t", user(DIP(super, dp, uid))->name);
502 			/* now skip whitespace */
503 			while ((c = getchar()) == ' ' || c == '\t');
504 			/* and print out the remainder of the input line */
505 			while (c != EOF && c != '\n') {
506 				putchar(c);
507 				c = getchar();
508 			}
509 			putchar('\n');
510 			inode1 = inode;
511 		} else {
512 			if (errno)
513 				errx(1, "%s", name);
514 			/* skip this line */
515 			while ((c = getchar()) != EOF && c != '\n');
516 		}
517 		if (c == EOF)
518 			break;
519 	}
520 }
521 
522 static void
523 usage()
524 {
525 #ifdef	COMPAT
526 	fprintf(stderr, "usage: quot [-nfcvha] [filesystem ...]\n");
527 #else	/* COMPAT */
528 	fprintf(stderr, "usage: quot [ -acfhknv ] [ filesystem ... ]\n");
529 #endif	/* COMPAT */
530 	exit(1);
531 }
532 
533 /*
534  * Sanity checks for old file systems.
535  * Stolen from <sys/lib/libsa/ufs.c>
536  */
537 static void
538 ffs_oldfscompat(fs)
539 	struct fs *fs;
540 {
541 	int i;
542 
543 	if (fs->fs_old_inodefmt < FS_44INODEFMT) {
544 		quad_t sizepb = fs->fs_bsize;
545 
546 		fs->fs_maxfilesize = fs->fs_bsize * NDADDR - 1;
547 		for (i = 0; i < NIADDR; i++) {
548 			sizepb *= NINDIR(fs);
549 			fs->fs_maxfilesize += sizepb;
550 		}
551 		fs->fs_qbmask = ~fs->fs_bmask;
552 		fs->fs_qfmask = ~fs->fs_fmask;
553 	}
554 }
555 
556 /*
557  * Possible superblock locations ordered from most to least likely.
558  */
559 static int sblock_try[] = SBLOCKSEARCH;
560 static char superblock[SBLOCKSIZE];
561 
562 
563 void
564 quot(name, mp)
565 	char *name, *mp;
566 {
567 	int fd, i;
568 	struct fs *fs;
569 	int sbloc;
570 
571 	get_inode(-1, 0, 0);		/* flush cache */
572 	inituser();
573 	initfsizes();
574 	if ((fd = open(name, 0)) < 0) {
575 		warn("%s", name);
576 		return;
577 	}
578 
579 	for (i = 0; ; i++) {
580 		sbloc = sblock_try[i];
581 		if (sbloc == -1) {
582 			warnx("%s: not a BSD filesystem", name);
583 			close(fd);
584 			return;
585 		}
586 		if (pread(fd, superblock, SBLOCKSIZE, sbloc) != SBLOCKSIZE)
587 			continue;
588 		fs = (struct fs *)superblock;
589 
590 		if (fs->fs_magic != FS_UFS1_MAGIC &&
591 		    fs->fs_magic != FS_UFS2_MAGIC)
592 			continue;
593 
594 		if (fs->fs_magic == FS_UFS2_MAGIC
595 		    || fs->fs_old_flags & FS_FLAGS_UPDATED) {
596 			/* Not the main superblock */
597 			if (fs->fs_sblockloc != sbloc)
598 				continue;
599 		} else {
600 			/* might be a first alt. id blocksize 64k */
601 			if (sbloc == SBLOCK_UFS2)
602 				continue;
603 		}
604 
605 		if (fs->fs_bsize > MAXBSIZE ||
606 		    fs->fs_bsize < sizeof(struct fs))
607 			continue;
608 		break;
609 	}
610 
611 	ffs_oldfscompat((struct fs *)superblock);
612 	printf("%s:", name);
613 	if (mp)
614 		printf(" (%s)", mp);
615 	putchar('\n');
616 	(*func)(fd, fs, name);
617 	close(fd);
618 }
619 
620 int
621 main(argc, argv)
622 	int argc;
623 	char **argv;
624 {
625 	char all = 0;
626 	struct statvfs *mp;
627 	char dev[MNAMELEN + 1];
628 	char *nm;
629 	int cnt;
630 
631 	func = douser;
632 #ifndef	COMPAT
633 	header = getbsize(NULL, &blocksize);
634 #endif
635 	while (--argc > 0 && **++argv == '-') {
636 		while (*++*argv) {
637 			switch (**argv) {
638 			case 'n':
639 				func = donames;
640 				break;
641 			case 'c':
642 				func = dofsizes;
643 				break;
644 			case 'a':
645 				all = 1;
646 				break;
647 			case 'f':
648 				count = 1;
649 				break;
650 			case 'h':
651 				estimate = 1;
652 				break;
653 #ifndef	COMPAT
654 			case 'k':
655 				blocksize = 1024;
656 				break;
657 #endif	/* COMPAT */
658 			case 'v':
659 				unused = 1;
660 				break;
661 			default:
662 				usage();
663 			}
664 		}
665 	}
666 	if (all) {
667 		cnt = getmntinfo(&mp, MNT_NOWAIT);
668 		for (; --cnt >= 0; mp++) {
669 			if (!strncmp(mp->f_fstypename, MOUNT_FFS,
670 			    sizeof(mp->f_fstypename))) {
671 				if ((nm =
672 				    strrchr(mp->f_mntfromname, '/')) != NULL) {
673 					sprintf(dev, "/dev/r%s", nm + 1);
674 					nm = dev;
675 				} else
676 					nm = mp->f_mntfromname;
677 				quot(nm, mp->f_mntonname);
678 			}
679 		}
680 	}
681 	while (--argc >= 0)
682 		quot(*argv++, 0);
683 	return 0;
684 }
685