quot.c revision 114009
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
33static const char rcsid[] =
34  "$FreeBSD: head/usr.sbin/quot/quot.c 114009 2003-04-25 10:07:50Z tjr $";
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: */
57static char estimate;
58static char count;
59static char unused;
60static void (*func)(int, struct fs *, char *);
61static long blocksize;
62static char *header;
63static int headerlen;
64
65static union dinode *get_inode(int, struct fs *, ino_t);
66static int	virtualblocks(struct fs *, union dinode *);
67static int	isfree(struct fs *, union dinode *);
68static void	inituser(void);
69static void	usrrehash(void);
70static struct user *user(uid_t);
71static int	cmpusers(const void *, const void *);
72static void	uses(uid_t, daddr_t, time_t);
73static void	initfsizes(void);
74static void	dofsizes(int, struct fs *, char *);
75static void	douser(int, struct fs *, char *);
76static void	donames(int, struct fs *, char *);
77static void	usage(void);
78static 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
102union 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
110static union dinode *
111get_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
150static 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
184static int
185isfree(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
205static 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;
214static int nusers;
215
216static void
217inituser()
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
235static void
236usrrehash()
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
256static struct user *
257user(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
293static int
294cmpusers(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
307static void
308uses(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
336struct 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
343static void
344initfsizes()
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
357static void
358dofsizes(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
432static void
433douser(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
475static void
476donames(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
522static void
523usage()
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 */
536static int sblock_try[] = SBLOCKSEARCH;
537static char superblock[SBLOCKSIZE];
538
539void
540quot(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 == 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
584int
585main(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