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