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