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