xref: /freebsd/usr.sbin/quot/quot.c (revision 1ce6b93097d994e7388957bdad07c32c5ad642c6)
1 /*
2  * Copyright (C) 1991, 1994 Wolfgang Solfrank.
3  * Copyright (C) 1991, 1994 TooLs GmbH.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *	This product includes software developed by TooLs GmbH.
17  * 4. The name of TooLs GmbH may not be used to endorse or promote products
18  *    derived from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
26  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
28  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
29  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #ifndef lint
33 static const char rcsid[] =
34   "$FreeBSD$";
35 #endif /* not lint */
36 
37 #include <sys/param.h>
38 #include <sys/stdint.h>
39 #include <sys/mount.h>
40 #include <sys/disklabel.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 <fcntl.h>
47 #include <fstab.h>
48 #include <errno.h>
49 #include <paths.h>
50 #include <pwd.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.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 long blocksize;
62 static char *header;
63 static size_t headerlen;
64 
65 static union dinode *get_inode(int, struct fs *, ino_t);
66 static int	virtualblocks(struct fs *, union dinode *);
67 static int	isfree(struct fs *, union dinode *);
68 static void	inituser(void);
69 static void	usrrehash(void);
70 static struct user *user(uid_t);
71 static int	cmpusers(const void *, const void *);
72 static void	uses(uid_t, daddr_t, time_t);
73 static void	initfsizes(void);
74 static void	dofsizes(int, struct fs *, char *);
75 static void	douser(int, struct fs *, char *);
76 static void	donames(int, struct fs *, char *);
77 static void	usage(void);
78 static void	quot(char *, char *);
79 
80 /*
81  * Original BSD quot doesn't round to number of frags/blocks,
82  * doesn't account for indirection blocks and gets it totally
83  * wrong if the	size is a multiple of the blocksize.
84  * The new code always counts the number of 512 byte blocks
85  * instead of the number of kilobytes and converts them	to
86  * kByte when done (on request).
87  *
88  * Due to the size of modern disks, we must cast intermediate
89  * values to 64 bits to prevent potential overflows.
90  */
91 #ifdef	COMPAT
92 #define	SIZE(n)	(n)
93 #else
94 #define	SIZE(n) ((int)(((quad_t)(n) * 512 + blocksize - 1)/blocksize))
95 #endif
96 
97 #define	INOCNT(fs)	((fs)->fs_ipg)
98 #define	INOSZ(fs) \
99 	(((fs)->fs_magic == FS_UFS1_MAGIC ? sizeof(struct ufs1_dinode) : \
100 	sizeof(struct ufs2_dinode)) * INOCNT(fs))
101 
102 union dinode {
103 	struct ufs1_dinode dp1;
104 	struct ufs2_dinode dp2;
105 };
106 #define	DIP(fs, dp, field) \
107 	(((fs)->fs_magic == FS_UFS1_MAGIC) ? \
108 	(dp)->dp1.field : (dp)->dp2.field)
109 
110 static union dinode *
111 get_inode(fd,super,ino)
112 	int fd;
113 	struct fs *super;
114 	ino_t ino;
115 {
116 	static caddr_t ipbuf;
117 	static ino_t last;
118 
119 	if (fd < 0) {		/* flush cache */
120 		if (ipbuf) {
121 			free(ipbuf);
122 			ipbuf = 0;
123 		}
124 		return 0;
125 	}
126 
127 	if (!ipbuf || ino < last || ino >= last + INOCNT(super)) {
128 		if (!ipbuf
129 		    && !(ipbuf = malloc(INOSZ(super))))
130 			errx(1, "allocate inodes");
131 		last = (ino / INOCNT(super)) * INOCNT(super);
132 		if (lseek(fd, (off_t)ino_to_fsba(super, last) << super->fs_fshift, 0) < (off_t)0
133 		    || read(fd, ipbuf, INOSZ(super)) != (ssize_t)INOSZ(super))
134 			err(1, "read inodes");
135 	}
136 
137 	if (super->fs_magic == FS_UFS1_MAGIC)
138 		return ((union dinode *)
139 		    &((struct ufs1_dinode *)ipbuf)[ino % INOCNT(super)]);
140 	return ((union dinode *)
141 	    &((struct ufs2_dinode *)ipbuf)[ino % INOCNT(super)]);
142 }
143 
144 #ifdef	COMPAT
145 #define	actualblocks(fs, dp)	(DIP(fs, dp, di_blocks) / 2)
146 #else
147 #define	actualblocks(fs, dp)	DIP(fs, dp, di_blocks)
148 #endif
149 
150 static int virtualblocks(super, dp)
151 	struct fs *super;
152 	union dinode *dp;
153 {
154 	register off_t nblk, sz;
155 
156 	sz = DIP(super, dp, di_size);
157 #ifdef	COMPAT
158 	if (lblkno(super,sz) >= NDADDR) {
159 		nblk = blkroundup(super,sz);
160 		if (sz == nblk)
161 			nblk += super->fs_bsize;
162 	}
163 
164 	return sz / 1024;
165 
166 #else	/* COMPAT */
167 
168 	if (lblkno(super,sz) >= NDADDR) {
169 		nblk = blkroundup(super,sz);
170 		sz = lblkno(super,nblk);
171 		sz = (sz - NDADDR + NINDIR(super) - 1) / NINDIR(super);
172 		while (sz > 0) {
173 			nblk += sz * super->fs_bsize;
174 			/* sz - 1 rounded up */
175 			sz = (sz - 1 + NINDIR(super) - 1) / NINDIR(super);
176 		}
177 	} else
178 		nblk = fragroundup(super,sz);
179 
180 	return nblk / 512;
181 #endif	/* COMPAT */
182 }
183 
184 static int
185 isfree(super, dp)
186 	struct fs *super;
187 	union dinode *dp;
188 {
189 #ifdef	COMPAT
190 	return (DIP(super, dp, di_mode) & IFMT) == 0;
191 #else	/* COMPAT */
192 
193 	switch (DIP(super, dp, di_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 	register int i;
220 	register 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 	register int i;
239 	register 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)); usrn->name;
248 		    usrn--) {
249 			if (usrn <= users)
250 				usrn = users + nusers;
251 		}
252 		*usrn = *usr;
253 	}
254 }
255 
256 static struct user *
257 user(uid)
258 	uid_t uid;
259 {
260 	register struct user *usr;
261 	register int i;
262 	struct passwd *pwd;
263 
264 	while (1) {
265 		for (usr = users + (uid&(nusers - 1)), i = nusers; --i >= 0;
266 		    usr--) {
267 			if (!usr->name) {
268 				usr->uid = uid;
269 
270 				if (!(pwd = getpwuid(uid))) {
271 					if ((usr->name = (char *)malloc(7)))
272 						sprintf(usr->name,"#%d",uid);
273 				} else {
274 					if ((usr->name = (char *)
275 					    malloc(strlen(pwd->pw_name) + 1)))
276 						strcpy(usr->name,pwd->pw_name);
277 				}
278 				if (!usr->name)
279 					errx(1, "allocate users");
280 
281 				return usr;
282 
283 			} else if (usr->uid == uid)
284 				return usr;
285 
286 			if (usr <= users)
287 				usr = users + nusers;
288 		}
289 		usrrehash();
290 	}
291 }
292 
293 static int
294 cmpusers(v1,v2)
295 	const void *v1, *v2;
296 {
297 	const struct user *u1, *u2;
298 	u1 = (const struct user *)v1;
299 	u2 = (const struct user *)v2;
300 
301 	return u2->space - u1->space;
302 }
303 
304 #define	sortusers(users)	(qsort((users),nusers,sizeof(struct user), \
305 				    cmpusers))
306 
307 static void
308 uses(uid,blks,act)
309 	uid_t uid;
310 	daddr_t blks;
311 	time_t act;
312 {
313 	static time_t today;
314 	register struct user *usr;
315 
316 	if (!today)
317 		time(&today);
318 
319 	usr = user(uid);
320 	usr->count++;
321 	usr->space += blks;
322 
323 	if (today - act > 90L * 24L * 60L * 60L)
324 		usr->spc90 += blks;
325 	if (today - act > 60L * 24L * 60L * 60L)
326 		usr->spc60 += blks;
327 	if (today - act > 30L * 24L * 60L * 60L)
328 		usr->spc30 += blks;
329 }
330 
331 #ifdef	COMPAT
332 #define	FSZCNT	500
333 #else
334 #define	FSZCNT	512
335 #endif
336 struct fsizes {
337 	struct fsizes *fsz_next;
338 	daddr_t fsz_first, fsz_last;
339 	ino_t fsz_count[FSZCNT];
340 	daddr_t fsz_sz[FSZCNT];
341 } *fsizes;
342 
343 static void
344 initfsizes()
345 {
346 	register struct fsizes *fp;
347 	register int i;
348 
349 	for (fp = fsizes; fp; fp = fp->fsz_next) {
350 		for (i = FSZCNT; --i >= 0;) {
351 			fp->fsz_count[i] = 0;
352 			fp->fsz_sz[i] = 0;
353 		}
354 	}
355 }
356 
357 static void
358 dofsizes(fd, super, name)
359 	int fd;
360 	struct fs *super;
361 	char *name;
362 {
363 	ino_t inode, maxino;
364 	union dinode *dp;
365 	daddr_t sz, ksz;
366 	struct fsizes *fp, **fsp;
367 	register int i;
368 
369 	maxino = super->fs_ncg * super->fs_ipg - 1;
370 #ifdef	COMPAT
371 	if (!(fsizes = (struct fsizes *)malloc(sizeof(struct fsizes))))
372 		errx(1, "allocate fsize structure");
373 #endif	/* COMPAT */
374 	for (inode = 0; inode < maxino; inode++) {
375 		errno = 0;
376 		if ((dp = get_inode(fd,super,inode))
377 #ifdef	COMPAT
378 		    && ((DIP(super, dp, di_mode) & IFMT) == IFREG
379 			|| (DIP(super, dp, di_mode) & IFMT) == IFDIR)
380 #else	/* COMPAT */
381 		    && !isfree(super, dp)
382 #endif	/* COMPAT */
383 		    ) {
384 			sz = estimate ? virtualblocks(super, dp) :
385 			    actualblocks(super, dp);
386 #ifdef	COMPAT
387 			if (sz >= FSZCNT) {
388 				fsizes->fsz_count[FSZCNT-1]++;
389 				fsizes->fsz_sz[FSZCNT-1] += sz;
390 			} else {
391 				fsizes->fsz_count[sz]++;
392 				fsizes->fsz_sz[sz] += sz;
393 			}
394 #else	/* COMPAT */
395 			ksz = SIZE(sz);
396 			for (fsp = &fsizes; (fp = *fsp); 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, "allocate 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 			err(1, "%s", name);
418 		}
419 	}
420 	sz = 0;
421 	for (fp = fsizes; fp; fp = fp->fsz_next) {
422 		for (i = 0; i < FSZCNT; i++) {
423 			if (fp->fsz_count[i])
424 				printf("%jd\t%jd\t%d\n",
425 				    (intmax_t)(fp->fsz_first + i),
426 				    (intmax_t)fp->fsz_count[i],
427 				    SIZE(sz += fp->fsz_sz[i]));
428 		}
429 	}
430 }
431 
432 static void
433 douser(fd, super, name)
434 	int fd;
435 	struct fs *super;
436 	char *name;
437 {
438 	ino_t inode, maxino;
439 	struct user *usr, *usrs;
440 	union dinode *dp;
441 	register int n;
442 
443 	maxino = super->fs_ncg * super->fs_ipg - 1;
444 	for (inode = 0; inode < maxino; inode++) {
445 		errno = 0;
446 		if ((dp = get_inode(fd,super,inode))
447 		    && !isfree(super, dp))
448 			uses(DIP(super, dp, di_uid),
449 			    estimate ? virtualblocks(super, dp) :
450 				actualblocks(super, dp),
451 			    DIP(super, dp, di_atime));
452 		else if (errno) {
453 			err(1, "%s", name);
454 		}
455 	}
456 	if (!(usrs = (struct user *)malloc(nusers * sizeof(struct user))))
457 		errx(1, "allocate users");
458 	bcopy(users,usrs,nusers * sizeof(struct user));
459 	sortusers(usrs);
460 	for (usr = usrs, n = nusers; --n >= 0 && usr->count; usr++) {
461 		printf("%5d",SIZE(usr->space));
462 		if (count)
463 			printf("\t%5ld",usr->count);
464 		printf("\t%-8s",usr->name);
465 		if (unused)
466 			printf("\t%5d\t%5d\t%5d",
467 			       SIZE(usr->spc30),
468 			       SIZE(usr->spc60),
469 			       SIZE(usr->spc90));
470 		printf("\n");
471 	}
472 	free(usrs);
473 }
474 
475 static void
476 donames(fd, super, name)
477 	int fd;
478 	struct fs *super;
479 	char *name;
480 {
481 	int c;
482 	ino_t inode, inode1;
483 	ino_t maxino;
484 	union dinode *dp;
485 
486 	maxino = super->fs_ncg * super->fs_ipg - 1;
487 	/* first skip the name of the filesystem */
488 	while ((c = getchar()) != EOF && (c < '0' || c > '9'))
489 		while ((c = getchar()) != EOF && c != '\n');
490 	ungetc(c,stdin);
491 	inode1 = -1;
492 	while (scanf("%u",&inode) == 1) {
493 		if (inode > maxino) {
494 			warnx("illegal inode %d",inode);
495 			return;
496 		}
497 		errno = 0;
498 		if ((dp = get_inode(fd,super,inode))
499 		    && !isfree(super, dp)) {
500 			printf("%s\t",user(DIP(super, dp, di_uid))->name);
501 			/* now skip whitespace */
502 			while ((c = getchar()) == ' ' || c == '\t');
503 			/* and print out the remainder of the input line */
504 			while (c != EOF && c != '\n') {
505 				putchar(c);
506 				c = getchar();
507 			}
508 			putchar('\n');
509 			inode1 = inode;
510 		} else {
511 			if (errno) {
512 				err(1, "%s", name);
513 			}
514 			/* skip this line */
515 			while ((c = getchar()) != EOF && c != '\n');
516 		}
517 		if (c == EOF)
518 			break;
519 	}
520 }
521 
522 static void
523 usage()
524 {
525 #ifdef	COMPAT
526 	fprintf(stderr,"usage: quot [-nfcvha] [filesystem ...]\n");
527 #else	/* COMPAT */
528 	fprintf(stderr,"usage: quot [-acfhknv] [filesystem ...]\n");
529 #endif	/* COMPAT */
530 	exit(1);
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 void
540 quot(name,mp)
541 	char *name, *mp;
542 {
543 	int i, fd;
544 	struct fs *fs;
545 
546 	get_inode(-1, NULL, 0);		/* flush cache */
547 	inituser();
548 	initfsizes();
549 	if ((fd = open(name,0)) < 0) {
550 		warn("%s", name);
551 		close(fd);
552 		return;
553 	}
554 	for (i = 0; sblock_try[i] != -1; i++) {
555 		if (lseek(fd, sblock_try[i], 0) != sblock_try[i]) {
556 			close(fd);
557 			return;
558 		}
559 		if (read(fd, superblock, SBLOCKSIZE) != SBLOCKSIZE) {
560 			close(fd);
561 			return;
562 		}
563 		fs = (struct fs *)superblock;
564 		if ((fs->fs_magic == FS_UFS1_MAGIC ||
565 		     (fs->fs_magic == FS_UFS2_MAGIC &&
566 		      fs->fs_sblockloc == numfrags(fs, sblock_try[i]))) &&
567 		    fs->fs_bsize <= MAXBSIZE &&
568 		    fs->fs_bsize >= sizeof(struct fs))
569 			break;
570 	}
571 	if (sblock_try[i] == -1) {
572 		warnx("%s: not a BSD filesystem",name);
573 		close(fd);
574 		return;
575 	}
576 	printf("%s:",name);
577 	if (mp)
578 		printf(" (%s)",mp);
579 	putchar('\n');
580 	(*func)(fd, fs, name);
581 	close(fd);
582 }
583 
584 int
585 main(argc,argv)
586 	int argc;
587 	char **argv;
588 {
589 	char all = 0;
590 	struct statfs *mp;
591 	struct fstab *fs;
592 	char dev[MNAMELEN + 1];
593 	char *nm;
594 	int cnt;
595 
596 	func = douser;
597 #ifndef	COMPAT
598 	header = getbsize(&headerlen,&blocksize);
599 #endif
600 	while (--argc > 0 && **++argv == '-') {
601 		while (*++*argv) {
602 			switch (**argv) {
603 			case 'n':
604 				func = donames;
605 				break;
606 			case 'c':
607 				func = dofsizes;
608 				break;
609 			case 'a':
610 				all = 1;
611 				break;
612 			case 'f':
613 				count = 1;
614 				break;
615 			case 'h':
616 				estimate = 1;
617 				break;
618 #ifndef	COMPAT
619 			case 'k':
620 				blocksize = 1024;
621 				break;
622 #endif	/* COMPAT */
623 			case 'v':
624 				unused = 1;
625 				break;
626 			default:
627 				usage();
628 			}
629 		}
630 	}
631 	if (all) {
632 		cnt = getmntinfo(&mp,MNT_NOWAIT);
633 		for (; --cnt >= 0; mp++) {
634 			if (!strncmp(mp->f_fstypename, "ufs", MFSNAMELEN)) {
635 				if ((nm = strrchr(mp->f_mntfromname,'/'))) {
636 					sprintf(dev,"%s%s",_PATH_DEV,nm + 1);
637 					nm = dev;
638 				} else
639 					nm = mp->f_mntfromname;
640 				quot(nm,mp->f_mntonname);
641 			}
642 		}
643 	}
644 	while (--argc >= 0) {
645 		if ((fs = getfsfile(*argv)) != NULL)
646 			quot(fs->fs_spec, 0);
647 		else
648 			quot(*argv,0);
649 		argv++;
650 	}
651 	return 0;
652 }
653