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