xref: /openbsd-src/usr.sbin/quot/quot.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: quot.c,v 1.10 2000/11/21 04:23:09 millert Exp $	*/
2 /*	$NetBSD: quot.c,v 1.7.4.1 1996/05/31 18:06:36 jtc Exp $	*/
3 
4 /*
5  * Copyright (C) 1991, 1994 Wolfgang Solfrank.
6  * Copyright (C) 1991, 1994 TooLs GmbH.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgement:
19  *	This product includes software developed by TooLs GmbH.
20  * 4. The name of TooLs GmbH may not be used to endorse or promote products
21  *    derived from this software without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR
24  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26  * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
29  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
30  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
31  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
32  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33  */
34 
35 #ifndef lint
36 static char rcsid[] = "$Id: quot.c,v 1.10 2000/11/21 04:23:09 millert Exp $";
37 #endif /* not lint */
38 
39 #include <sys/param.h>
40 #include <sys/mount.h>
41 #include <sys/time.h>
42 #include <ufs/ffs/fs.h>
43 #include <ufs/ufs/quota.h>
44 #include <ufs/ufs/inode.h>
45 
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <err.h>
50 #include <errno.h>
51 #include <fcntl.h>
52 #include <paths.h>
53 #include <pwd.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)();
61 static long blocksize;
62 static char *header;
63 static int headerlen;
64 
65 /*
66  * Original BSD quot doesn't round to number of frags/blocks,
67  * doesn't account for indirection blocks and gets it totally
68  * wrong if the	size is a multiple of the blocksize.
69  * The new code always counts the number of DEV_BSIZE byte blocks
70  * instead of the number of kilobytes and converts them	to
71  * KByte when done (on request).
72  */
73 #ifdef	COMPAT
74 #define	SIZE(n)	(n)
75 #else
76 #define	SIZE(n)	(howmany(((off_t)(n)) * DEV_BSIZE, blocksize))
77 #endif
78 
79 #define	INOCNT(fs)	((fs)->fs_ipg)
80 #define	INOSZ(fs)	(sizeof(struct dinode) * INOCNT(fs))
81 
82 static struct dinode *
83 get_inode(fd, super, ino)
84 	int fd;
85 	struct fs *super;
86 	ino_t ino;
87 {
88 	static struct dinode *ip;
89 	static ino_t last;
90 
91 	if (fd < 0) {		/* flush cache */
92 		if (ip) {
93 			free(ip);
94 			ip = 0;
95 		}
96 		return 0;
97 	}
98 
99 	if (!ip || ino < last || ino >= last + INOCNT(super)) {
100 		if (!ip && !(ip = (struct dinode *)malloc(INOSZ(super))))
101 			err(1, "allocate inodes");
102 		last = (ino / INOCNT(super)) * INOCNT(super);
103 		if (lseek(fd,
104 			  (off_t)ino_to_fsba(super, last) << super->fs_fshift,
105 			  0) < 0
106 		    || read(fd, ip, INOSZ(super)) != INOSZ(super)) {
107 			err(1, "read inodes");
108 		}
109 	}
110 
111 	return ip + ino % INOCNT(super);
112 }
113 
114 #ifdef	COMPAT
115 #define	actualblocks(super, ip)	((ip)->di_blocks / 2)
116 #else
117 #define	actualblocks(super, ip)	((ip)->di_blocks)
118 #endif
119 
120 static int
121 virtualblocks(super, ip)
122 	struct fs *super;
123 	struct dinode *ip;
124 {
125 	off_t nblk, sz;
126 
127 	sz = ip->di_size;
128 #ifdef	COMPAT
129 	if (lblkno(super, sz) >= NDADDR) {
130 		nblk = blkroundup(super, sz);
131 		if (sz == nblk)
132 			nblk += super->fs_bsize;
133 	}
134 
135 	return sz / 1024;
136 #else	/* COMPAT */
137 
138 	if (lblkno(super, sz) >= NDADDR) {
139 		nblk = blkroundup(super, sz);
140 		sz = lblkno(super, nblk);
141 		sz = howmany(sz - NDADDR, NINDIR(super));
142 		while (sz > 0) {
143 			nblk += sz * super->fs_bsize;
144 			/* One block on this level is in the inode itself */
145 			sz = howmany(sz - 1, NINDIR(super));
146 		}
147 	} else
148 		nblk = fragroundup(super, sz);
149 
150 	return nblk / DEV_BSIZE;
151 #endif	/* COMPAT */
152 }
153 
154 static int
155 isfree(ip)
156 	struct dinode *ip;
157 {
158 #ifdef	COMPAT
159 	return (ip->di_mode&IFMT) == 0;
160 #else	/* COMPAT */
161 	switch (ip->di_mode&IFMT) {
162 	case IFIFO:
163 	case IFLNK:		/* should check FASTSYMLINK? */
164 	case IFDIR:
165 	case IFREG:
166 		return 0;
167 	default:
168 		return 1;
169 	}
170 #endif
171 }
172 
173 static struct user {
174 	uid_t uid;
175 	char *name;
176 	daddr_t space;
177 	long count;
178 	daddr_t spc30;
179 	daddr_t spc60;
180 	daddr_t spc90;
181 } *users;
182 static int nusers;
183 
184 static void
185 inituser()
186 {
187 	int i;
188 	struct user *usr;
189 
190 	if (!nusers) {
191 		nusers = 8;
192 		if (!(users =
193 		    (struct user *)calloc(nusers, sizeof(struct user)))) {
194 			err(1, "allocate users");
195 		}
196 	} else {
197 		for (usr = users, i = nusers; --i >= 0; usr++) {
198 			usr->space = usr->spc30 = usr->spc60 = usr->spc90 = 0;
199 			usr->count = 0;
200 		}
201 	}
202 }
203 
204 static void
205 usrrehash()
206 {
207 	int i;
208 	struct user *usr, *usrn;
209 	struct user *svusr;
210 
211 	svusr = users;
212 	nusers <<= 1;
213 	if (!(users = (struct user *)calloc(nusers, sizeof(struct user))))
214 		err(1, "allocate users");
215 	for (usr = svusr, i = nusers >> 1; --i >= 0; usr++) {
216 		for (usrn = users + (usr->uid&(nusers - 1));
217 		     usrn->name;
218 		    usrn--) {
219 			if (usrn <= users)
220 				usrn = users + nusers;
221 		}
222 		*usrn = *usr;
223 	}
224 }
225 
226 static struct user *
227 user(uid)
228 	uid_t uid;
229 {
230 	int i;
231 	struct passwd *pwd;
232 	struct user *usr;
233 
234 	while (1) {
235 		for (usr = users + (uid&(nusers - 1)), i = nusers;
236 		     --i >= 0;
237 		    usr--) {
238 			if (!usr->name) {
239 				usr->uid = uid;
240 
241 				if (!(pwd = getpwuid(uid)))
242 					asprintf(&usr->name, "#%u", uid);
243 				else
244 					usr->name = strdup(pwd->pw_name);
245 				if (!usr->name)
246 					err(1, "allocate users");
247 				return usr;
248 			} else if (usr->uid == uid)
249 				return usr;
250 
251 			if (usr <= users)
252 				usr = users + nusers;
253 		}
254 		usrrehash();
255 	}
256 }
257 
258 static int
259 cmpusers(u1, u2)
260 	struct user *u1, *u2;
261 {
262 	return u2->space - u1->space;
263 }
264 
265 #define	sortusers(users)	(qsort((users), nusers, sizeof(struct user), \
266 				    cmpusers))
267 
268 static void
269 uses(uid, blks, act)
270 	uid_t uid;
271 	daddr_t blks;
272 	time_t act;
273 {
274 	static time_t today;
275 	struct user *usr;
276 
277 	if (!today)
278 		time(&today);
279 
280 	usr = user(uid);
281 	usr->count++;
282 	usr->space += blks;
283 
284 	if (today - act > 90L * 24L * 60L * 60L)
285 		usr->spc90 += blks;
286 	if (today - act > 60L * 24L * 60L * 60L)
287 		usr->spc60 += blks;
288 	if (today - act > 30L * 24L * 60L * 60L)
289 		usr->spc30 += blks;
290 }
291 
292 #ifdef	COMPAT
293 #define	FSZCNT	500
294 #else
295 #define	FSZCNT	512
296 #endif
297 struct fsizes {
298 	struct fsizes *fsz_next;
299 	daddr_t fsz_first, fsz_last;
300 	ino_t fsz_count[FSZCNT];
301 	daddr_t fsz_sz[FSZCNT];
302 } *fsizes;
303 
304 static void
305 initfsizes()
306 {
307 	struct fsizes *fp;
308 	int i;
309 
310 	for (fp = fsizes; fp; fp = fp->fsz_next) {
311 		for (i = FSZCNT; --i >= 0;) {
312 			fp->fsz_count[i] = 0;
313 			fp->fsz_sz[i] = 0;
314 		}
315 	}
316 }
317 
318 static void
319 dofsizes(fd, super, name)
320 	int fd;
321 	struct fs *super;
322 	char *name;
323 {
324 	ino_t inode, maxino;
325 	struct dinode *ip;
326 	daddr_t sz, ksz;
327 	struct fsizes *fp, **fsp;
328 	int i;
329 
330 	maxino = super->fs_ncg * super->fs_ipg - 1;
331 #ifdef	COMPAT
332 	if (!(fsizes = (struct fsizes *)malloc(sizeof(struct fsizes))))
333 		err(1, "alloc fsize structure");
334 #endif	/* COMPAT */
335 	for (inode = 0; inode < maxino; inode++) {
336 		errno = 0;
337 		if ((ip = get_inode(fd, super, inode))
338 #ifdef	COMPAT
339 		    && ((ip->di_mode&IFMT) == IFREG
340 			|| (ip->di_mode&IFMT) == IFDIR)
341 #else	/* COMPAT */
342 		    && !isfree(ip)
343 #endif	/* COMPAT */
344 		    ) {
345 			sz = estimate ? virtualblocks(super, ip) :
346 			    actualblocks(super, ip);
347 #ifdef	COMPAT
348 			if (sz >= FSZCNT) {
349 				fsizes->fsz_count[FSZCNT-1]++;
350 				fsizes->fsz_sz[FSZCNT-1] += sz;
351 			} else {
352 				fsizes->fsz_count[sz]++;
353 				fsizes->fsz_sz[sz] += sz;
354 			}
355 #else	/* COMPAT */
356 			ksz = SIZE(sz);
357 			for (fsp = &fsizes; (fp = *fsp); fsp = &fp->fsz_next) {
358 				if (ksz < fp->fsz_last)
359 					break;
360 			}
361 			if (!fp || ksz < fp->fsz_first) {
362 				if (!(fp = (struct fsizes *)
363 				    malloc(sizeof(struct fsizes)))) {
364 					err(1, "alloc fsize structure");
365 				}
366 				fp->fsz_next = *fsp;
367 				*fsp = fp;
368 				fp->fsz_first = (ksz / FSZCNT) * FSZCNT;
369 				fp->fsz_last = fp->fsz_first + FSZCNT;
370 				for (i = FSZCNT; --i >= 0;) {
371 					fp->fsz_count[i] = 0;
372 					fp->fsz_sz[i] = 0;
373 				}
374 			}
375 			fp->fsz_count[ksz % FSZCNT]++;
376 			fp->fsz_sz[ksz % FSZCNT] += sz;
377 #endif	/* COMPAT */
378 		} else if (errno)
379 			err(1, "%s", name);
380 	}
381 	sz = 0;
382 	for (fp = fsizes; fp; fp = fp->fsz_next) {
383 		for (i = 0; i < FSZCNT; i++) {
384 			if (fp->fsz_count[i])
385 				printf("%d\t%d\t%qd\n",
386 				       fp->fsz_first + i, fp->fsz_count[i],
387 				    (quad_t) SIZE(sz += fp->fsz_sz[i]));
388 		}
389 	}
390 }
391 
392 static void
393 douser(fd, super, name)
394 	int fd;
395 	struct fs *super;
396 	char *name;
397 {
398 	ino_t inode, maxino;
399 	struct user *usr, *usrs;
400 	struct dinode *ip;
401 	int n;
402 
403 	maxino = super->fs_ncg * super->fs_ipg - 1;
404 	for (inode = 0; inode < maxino; inode++) {
405 		errno = 0;
406 		if ((ip = get_inode(fd, super, inode))
407 		    && !isfree(ip))
408 			uses(ip->di_uid,
409 			     estimate ? virtualblocks(super, ip)
410 				: actualblocks(super, ip),
411 			    ip->di_atime);
412 		else if (errno)
413 			err(1, "%s", name);
414 	}
415 	if (!(usrs = (struct user *)malloc(nusers * sizeof(struct user))))
416 		err(1, "allocate users");
417 	memcpy(usrs, users, nusers * sizeof(struct user));
418 	sortusers(usrs);
419 	for (usr = usrs, n = nusers; --n >= 0 && usr->count; usr++) {
420 		printf("%5qd", (quad_t) SIZE(usr->space));
421 		if (count)
422 			printf("\t%5ld", usr->count);
423 		printf("\t%-8s", usr->name);
424 		if (unused)
425 			printf("\t%5qd\t%5qd\t%5qd",
426 			       (quad_t) SIZE(usr->spc30),
427 			       (quad_t) SIZE(usr->spc60),
428 			       (quad_t) SIZE(usr->spc90));
429 		printf("\n");
430 	}
431 	free(usrs);
432 }
433 
434 static void
435 donames(fd, super, name)
436 	int fd;
437 	struct fs *super;
438 	char *name;
439 {
440 	int c;
441 	ino_t inode, inode1;
442 	ino_t maxino;
443 	struct dinode *ip;
444 
445 	maxino = super->fs_ncg * super->fs_ipg - 1;
446 	/* first skip the name of the filesystem */
447 	while ((c = getchar()) != EOF && (c < '0' || c > '9'))
448 		while ((c = getchar()) != EOF && c != '\n');
449 	ungetc(c, stdin);
450 	inode1 = -1;
451 	while (scanf("%d", &inode) == 1) {
452 		if (inode < 0 || inode > maxino) {
453 #ifndef	COMPAT
454 			fprintf(stderr, "invalid inode %d\n", inode);
455 #endif
456 			return;
457 		}
458 #ifdef	COMPAT
459 		if (inode < inode1)
460 			continue;
461 #endif
462 		errno = 0;
463 		if ((ip = get_inode(fd, super, inode))
464 		    && !isfree(ip)) {
465 			printf("%s\t", user(ip->di_uid)->name);
466 			/* now skip whitespace */
467 			while ((c = getchar()) == ' ' || c == '\t');
468 			/* and print out the remainder of the input line */
469 			while (c != EOF && c != '\n') {
470 				putchar(c);
471 				c = getchar();
472 			}
473 			putchar('\n');
474 			inode1 = inode;
475 		} else {
476 			if (errno)
477 				err(1, "%s", name);
478 			/* skip this line */
479 			while ((c = getchar()) != EOF && c != '\n')
480 				;
481 		}
482 		if (c == EOF)
483 			break;
484 	}
485 }
486 
487 static void
488 usage()
489 {
490 #ifdef	COMPAT
491 	fprintf(stderr, "Usage: quot [-nfcvha] [filesystem ...]\n");
492 #else	/* COMPAT */
493 	fprintf(stderr, "Usage: quot [ -acfhknv ] [ filesystem ... ]\n");
494 #endif	/* COMPAT */
495 	exit(1);
496 }
497 
498 static char superblock[SBSIZE];
499 
500 #define	max(a,b)	MAX((a),(b))
501 /*
502  * Sanity checks for old file systems.
503  * Stolen from <sys/lib/libsa/ufs.c>
504  */
505 static void
506 ffs_oldfscompat(fs)
507 	struct fs *fs;
508 {
509 	int i;
510 
511 	fs->fs_npsect = max(fs->fs_npsect, fs->fs_nsect);	/* XXX */
512 	fs->fs_interleave = max(fs->fs_interleave, 1);		/* XXX */
513 	if (fs->fs_postblformat == FS_42POSTBLFMT)		/* XXX */
514 		fs->fs_nrpos = 8;				/* XXX */
515 	if (fs->fs_inodefmt < FS_44INODEFMT) {			/* XXX */
516 		quad_t sizepb = fs->fs_bsize;			/* XXX */
517 								/* XXX */
518 		fs->fs_maxfilesize = fs->fs_bsize * NDADDR - 1;	/* XXX */
519 		for (i = 0; i < NIADDR; i++) {			/* XXX */
520 			sizepb *= NINDIR(fs);			/* XXX */
521 			fs->fs_maxfilesize += sizepb;		/* XXX */
522 		}						/* XXX */
523 		fs->fs_qbmask = ~fs->fs_bmask;			/* XXX */
524 		fs->fs_qfmask = ~fs->fs_fmask;			/* XXX */
525 	}							/* XXX */
526 }
527 
528 void
529 quot(name, mp)
530 	char *name, *mp;
531 {
532 	int fd;
533 
534 	get_inode(-1);		/* flush cache */
535 	inituser();
536 	initfsizes();
537 	/*
538 	 * XXX this is completely broken.  Of course you can't read a
539 	 * directory, well, not anymore.  How to fix this, though...
540 	 */
541 	if ((fd = open(name, 0)) < 0) {
542 		warn("%s", name);
543 		return;
544 	}
545 	if (lseek(fd, SBOFF, 0) != SBOFF
546 	    || read(fd, superblock, SBSIZE) != SBSIZE
547 	    || ((struct fs *)superblock)->fs_magic != FS_MAGIC
548 	    || ((struct fs *)superblock)->fs_bsize > MAXBSIZE
549 	    || ((struct fs *)superblock)->fs_bsize < sizeof(struct fs)) {
550 		warnx("%s: not a BSD filesystem", name);
551 		close(fd);
552 		return;
553 	}
554 	ffs_oldfscompat(superblock);
555 	printf("%s:", name);
556 	if (mp)
557 		printf(" (%s)", mp);
558 	putchar('\n');
559 	(*func)(fd, superblock, name);
560 	close(fd);
561 }
562 
563 int
564 main(argc, argv)
565 	int argc;
566 	char **argv;
567 {
568 	int cnt, all, i;
569 	char dev[MNAMELEN], *nm, *mountpoint, *cp;
570 	struct statfs *mp;
571 
572 	all = 0;
573 	func = douser;
574 #ifndef	COMPAT
575 	header = getbsize(&headerlen, &blocksize);
576 #endif
577 	while (--argc > 0 && **++argv == '-') {
578 		while (*++*argv) {
579 			switch (**argv) {
580 			case 'n':
581 				func = donames;
582 				break;
583 			case 'c':
584 				func = dofsizes;
585 				break;
586 			case 'a':
587 				all = 1;
588 				break;
589 			case 'f':
590 				count = 1;
591 				break;
592 			case 'h':
593 				estimate = 1;
594 				break;
595 #ifndef	COMPAT
596 			case 'k':
597 				blocksize = 1024;
598 				break;
599 #endif	/* COMPAT */
600 			case 'v':
601 				unused = 1;
602 				break;
603 			default:
604 				usage();
605 			}
606 		}
607 	}
608 	cnt = getmntinfo(&mp, MNT_NOWAIT);
609 	if (all) {
610 		for (; --cnt >= 0; mp++) {
611 			if (strcmp(mp->f_fstypename, MOUNT_FFS) == 0 ||
612 			    strcmp(mp->f_fstypename, "ufs") == 0) {
613 				if ((nm = strrchr(mp->f_mntfromname, '/'))) {
614 					snprintf(dev, sizeof(dev), "%sr%s",
615 					    _PATH_DEV, nm + 1);
616 					nm = dev;
617 				} else
618 					nm = mp->f_mntfromname;
619 				quot(nm, mp->f_mntonname);
620 			}
621 		}
622 	}
623 	for (; --argc >= 0; argv++) {
624 		mountpoint = NULL;
625 		nm = *argv;
626 
627 		/* Remove trailing slashes from name. */
628 		cp = nm + strlen(nm);
629 		while (*(--cp) == '/' && cp != nm)
630 			*cp = '\0';
631 
632 		/* Look up the name in the mount table. */
633 		for (i = 0; i < cnt; i++) {
634 			/* Remove trailing slashes from name. */
635 			cp = mp[i].f_mntonname + strlen(mp[i].f_mntonname);
636 			while (*(--cp) == '/' && cp != mp[i].f_mntonname)
637 				*cp = '\0';
638 
639 			if ((!strcmp(mp->f_fstypename, MOUNT_FFS) ||
640 			     !strcmp(mp->f_fstypename, MOUNT_MFS) ||
641 			     !strcmp(mp->f_fstypename, "ufs")) &&
642 			    strcmp(nm, mp[i].f_mntonname) == 0) {
643 				nm = mp[i].f_mntfromname;
644 				mountpoint = mp[i].f_mntonname;
645 				break;
646 			}
647 		}
648 
649 		/* Make sure we have the raw device... */
650 		if (strncmp(nm, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0 &&
651 		    nm[sizeof(_PATH_DEV) - 1] != 'r') {
652 			snprintf(dev, sizeof(dev), "%sr%s", _PATH_DEV,
653 			    nm + sizeof(_PATH_DEV) - 1);
654 			nm = dev;
655 		}
656 		quot(nm, mountpoint);
657 	}
658 	exit(0);
659 }
660