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