xref: /illumos-gate/usr/src/cmd/fs.d/ufs/repquota/repquota.c (revision a38ddfee9c8c6b6c5a2947ff52fd2338362a4444)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved  	*/
28 
29 /*
30  * University Copyright- Copyright (c) 1982, 1986, 1988
31  * The Regents of the University of California
32  * All Rights Reserved
33  *
34  * University Acknowledgment- Portions of this document are derived from
35  * software developed by the University of California, Berkeley, and its
36  * contributors.
37  */
38 
39 #pragma ident	"%Z%%M%	%I%	%E% SMI"
40 
41 /*
42  * Quota report
43  */
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47 #include <string.h>
48 #include <strings.h>
49 #include <errno.h>
50 #include <sys/param.h>
51 #include <sys/types.h>
52 #include <fcntl.h>
53 #include <sys/filio.h>
54 #include <sys/mntent.h>
55 #include <sys/time.h>
56 #include <sys/fs/ufs_quota.h>
57 #include <sys/stat.h>
58 #include <sys/mnttab.h>
59 #include <sys/vfstab.h>
60 #include <pwd.h>
61 
62 #define	LOGINNAMESIZE	8
63 struct username {
64 	struct username *u_next;
65 	uid_t u_uid;
66 	char u_name[LOGINNAMESIZE + 1];
67 };
68 #define	UHASH 997
69 static struct username *uhead[UHASH];
70 
71 static struct username *lookup(uid_t);
72 static struct username *adduid(uid_t);
73 static int repquota(char *, char *, char *);
74 static void prquota(uid_t, struct dqblk *);
75 static void header(void);
76 static void usage(void);
77 static void fmttime(char *, long);
78 static char *hasvfsopt(struct vfstab *, char *);
79 static int quotactl(int, char *, uid_t, caddr_t);
80 static int oneof(char *, char **, int);
81 
82 extern char *mntopt();
83 extern char *hasmntopt();
84 
85 static int	vflag;		/* verbose */
86 static int	aflag;		/* all file systems */
87 static char **listbuf;
88 
89 #define	QFNAME "quotas"
90 #define	CHUNK	50
91 
92 #if DEV_BSIZE < 1024
93 #define	dbtok(x)	((x) / (1024 / DEV_BSIZE))
94 #else
95 #define	dbtok(x)	((x) * (DEV_BSIZE / 1024))
96 #endif
97 
98 int
99 main(int argc, char **argv)
100 {
101 	struct mnttab mntp;
102 	struct vfstab vfsbuf;
103 	char **listp;
104 	int listcnt;
105 	int listmax = 0;
106 	char quotafile[MAXPATHLEN];
107 	FILE *mtab, *vfstab;
108 	int errs = 0;
109 	int	opt;
110 
111 	if ((listbuf = malloc(sizeof (char *) * CHUNK)) == NULL) {
112 		(void) fprintf(stderr, "Can't alloc lisbuf array.");
113 		exit(31+1);
114 	}
115 	listmax = CHUNK;
116 	while ((opt = getopt(argc, argv, "avV")) != EOF)
117 		switch (opt) {
118 		case 'v':
119 			vflag++;
120 			break;
121 
122 		case 'a':
123 			aflag++;
124 			break;
125 
126 		case 'V': {
127 				/* Print command line */
128 				char	*optt;
129 				int	optc;
130 
131 				(void) printf("repquota -F ufs ");
132 				for (optc = 1; optc < argc; optc++) {
133 					optt = argv[optc];
134 					if (optt)
135 						(void) printf(" %s ", optt);
136 				}
137 				(void) putchar('\n');
138 			}
139 			break;
140 
141 		case '?':
142 		default:
143 			usage();
144 		}
145 
146 	if (argc <= optind && !aflag)
147 		usage();
148 
149 	/*
150 	 * Sync quota information to disk (as userdata).  On logging
151 	 * file systems, this operation does nothing because quota
152 	 * information is treated as metadata.  Logging file systems
153 	 * are dealt with below in repquota().
154 	 */
155 	if (quotactl(Q_ALLSYNC, NULL, 0, NULL) < 0 && errno == EINVAL && vflag)
156 		(void) printf("Warning: "
157 			"Quotas are not available in this kernel\n");
158 
159 	/*
160 	 * If aflag go through vfstab and make a list of appropriate
161 	 * filesystems.
162 	 */
163 	if (aflag) {
164 		listp = listbuf;
165 		listcnt = 0;
166 		if ((vfstab = fopen(VFSTAB, "r")) == NULL) {
167 			(void) fprintf(stderr, "Can't open ");
168 			perror(VFSTAB);
169 			exit(31+8);
170 		}
171 		while (getvfsent(vfstab, &vfsbuf) == 0) {
172 
173 			if (strcmp(vfsbuf.vfs_fstype, MNTTYPE_UFS) != 0 ||
174 			    (vfsbuf.vfs_mntopts == 0) ||
175 			    hasvfsopt(&vfsbuf, MNTOPT_RO) ||
176 			    (!hasvfsopt(&vfsbuf, MNTOPT_RQ) &&
177 			    !hasvfsopt(&vfsbuf, MNTOPT_QUOTA)))
178 				continue;
179 
180 			*listp = malloc(strlen(vfsbuf.vfs_special) + 1);
181 			(void) strcpy(*listp, vfsbuf.vfs_special);
182 			listp++;
183 			listcnt++;
184 			/* grow listbuf if needed */
185 			if (listcnt >= listmax) {
186 				listmax += CHUNK;
187 				listbuf = realloc(listbuf,
188 					sizeof (char *) * listmax);
189 				if (listbuf == NULL) {
190 					(void) fprintf(stderr,
191 						"Can't grow listbuf.\n");
192 					exit(31+1);
193 				}
194 				listp = &listbuf[listcnt];
195 			}
196 		}
197 		(void) fclose(vfstab);
198 		*listp = (char *)0;
199 		listp = listbuf;
200 	} else {
201 		listp = &argv[optind];
202 		listcnt = argc - optind;
203 	}
204 	if ((mtab = fopen(MNTTAB, "r")) == NULL) {
205 		(void) fprintf(stderr, "Can't open ");
206 		perror(MNTTAB);
207 		exit(31+8);
208 	}
209 	while (getmntent(mtab, &mntp) == 0) {
210 		if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) == 0 &&
211 		    !hasmntopt(&mntp, MNTOPT_RO) &&
212 		    (oneof(mntp.mnt_special, listp, listcnt) ||
213 		    oneof(mntp.mnt_mountp, listp, listcnt))) {
214 			(void) snprintf(quotafile, sizeof (quotafile), "%s/%s",
215 				mntp.mnt_mountp, QFNAME);
216 			errs += repquota(mntp.mnt_special,
217 				mntp.mnt_mountp, quotafile);
218 		}
219 	}
220 	(void) fclose(mtab);
221 	while (listcnt--) {
222 		if (*listp)
223 			(void) fprintf(stderr, "Cannot report on %s\n", *listp);
224 		listp++;
225 	}
226 	if (errs > 0)
227 		exit(31+1);
228 	return (0);
229 }
230 
231 static int
232 repquota(char *fsdev, char *fsfile, char *qffile)
233 {
234 	FILE *qf;
235 	uid_t uid;
236 	struct dqblk dqbuf;
237 	struct stat64 statb;
238 
239 	if (vflag || aflag)
240 		(void) printf("%s (%s):\n", fsdev, fsfile);
241 	qf = fopen64(qffile, "r");
242 	if (qf == NULL) {
243 		perror(qffile);
244 		return (1);
245 	}
246 	if (fstat64(fileno(qf), &statb) < 0) {
247 		perror(qffile);
248 		(void) fclose(qf);
249 		return (1);
250 	}
251 	/*
252 	 * Flush the file system. On logging file systems, this makes
253 	 * sure that the quota information (as metadata) gets rolled
254 	 * forward.
255 	 */
256 	if (ioctl(fileno(qf), _FIOFFS, NULL) == -1) {
257 		perror(qffile);
258 		(void) fprintf(stderr, "%s: cannot flush file system.\n",
259 				qffile);
260 		(void) fclose(qf);
261 		return (1);
262 	}
263 	header();
264 	for (uid = 0; uid <= MAXUID && uid >= 0; uid++) {
265 		(void) fread(&dqbuf, sizeof (struct dqblk), 1, qf);
266 		if (feof(qf))
267 			break;
268 		if (!vflag &&
269 		    dqbuf.dqb_curfiles == 0 && dqbuf.dqb_curblocks == 0)
270 			continue;
271 		prquota(uid, &dqbuf);
272 	}
273 	(void) fclose(qf);
274 	return (0);
275 }
276 
277 static void
278 header(void)
279 {
280 	(void) printf("                      Block limits"
281 		"                      File limits\n");
282 	(void) printf("User           used   soft   hard    timeleft"
283 		"    used   soft   hard    timeleft\n");
284 }
285 
286 static void
287 prquota(uid_t uid, struct dqblk *dqp)
288 {
289 	struct timeval tv;
290 	struct username *up;
291 	char ftimeleft[80], btimeleft[80];
292 
293 	if (dqp->dqb_bsoftlimit == 0 && dqp->dqb_bhardlimit == 0 &&
294 	    dqp->dqb_fsoftlimit == 0 && dqp->dqb_fhardlimit == 0)
295 		return;
296 	(void) time(&(tv.tv_sec));
297 	tv.tv_usec = 0;
298 	up = lookup(uid);
299 	if (up)
300 		(void) printf("%-10s", up->u_name);
301 	else
302 		(void) printf("#%-9ld", uid);
303 	if (dqp->dqb_bsoftlimit &&
304 	    dqp->dqb_curblocks >= dqp->dqb_bsoftlimit) {
305 		if (dqp->dqb_btimelimit == 0)
306 			(void) strcpy(btimeleft, "NOT STARTED");
307 		else if (dqp->dqb_btimelimit > tv.tv_sec)
308 			fmttime(btimeleft,
309 			    (long)(dqp->dqb_btimelimit - tv.tv_sec));
310 		else
311 			(void) strcpy(btimeleft, "EXPIRED");
312 	} else
313 		btimeleft[0] = '\0';
314 
315 	if (dqp->dqb_fsoftlimit && dqp->dqb_curfiles >= dqp->dqb_fsoftlimit) {
316 		if (dqp->dqb_ftimelimit == 0)
317 			(void) strcpy(ftimeleft, "NOT STARTED");
318 		else if (dqp->dqb_ftimelimit > tv.tv_sec)
319 			fmttime(ftimeleft,
320 			    (long)(dqp->dqb_ftimelimit - tv.tv_sec));
321 		else
322 			(void) strcpy(ftimeleft, "EXPIRED");
323 	} else
324 		ftimeleft[0] = '\0';
325 
326 	(void) printf("%c%c %6lu %6lu %6lu %11s %7lu %6lu %6lu %11s\n",
327 		(dqp->dqb_bsoftlimit &&
328 		    dqp->dqb_curblocks >= dqp->dqb_bsoftlimit) ? '+' : '-',
329 		(dqp->dqb_fsoftlimit &&
330 		    dqp->dqb_curfiles >= dqp->dqb_fsoftlimit) ? '+' : '-',
331 		dbtok(dqp->dqb_curblocks),
332 		dbtok(dqp->dqb_bsoftlimit),
333 		dbtok(dqp->dqb_bhardlimit),
334 		btimeleft,
335 		dqp->dqb_curfiles,
336 		dqp->dqb_fsoftlimit,
337 		dqp->dqb_fhardlimit,
338 		ftimeleft);
339 }
340 
341 static void
342 fmttime(char *buf, long time)
343 {
344 	int i;
345 	static struct {
346 		int c_secs;		/* conversion units in secs */
347 		char *c_str;		/* unit string */
348 	} cunits [] = {
349 		{60*60*24*28, "months"},
350 		{60*60*24*7, "weeks"},
351 		{60*60*24, "days"},
352 		{60*60, "hours"},
353 		{60, "mins"},
354 		{1, "secs"}
355 	};
356 
357 	if (time <= 0) {
358 		(void) strcpy(buf, "EXPIRED");
359 		return;
360 	}
361 	for (i = 0; i < sizeof (cunits) / sizeof (cunits[0]); i++) {
362 		if (time >= cunits[i].c_secs)
363 			break;
364 	}
365 	(void) sprintf(buf, "%.1f %s",
366 	    (double)time / cunits[i].c_secs, cunits[i].c_str);
367 }
368 
369 static int
370 oneof(char *target, char **olistp, int on)
371 {
372 	char **listp = olistp;
373 	int n = on;
374 
375 	while (n--) {
376 		if (*listp && strcmp(target, *listp) == 0) {
377 			*listp = (char *)0;
378 			return (1);
379 		}
380 		listp++;
381 	}
382 	return (0);
383 }
384 
385 static struct username *
386 lookup(uid_t uid)
387 {
388 	struct passwd *pwp;
389 	struct username *up;
390 
391 	for (up = uhead[uid % UHASH]; up != 0; up = up->u_next)
392 		if (up->u_uid == uid)
393 			return (up);
394 	if ((pwp = getpwuid((uid_t)uid)) == NULL)
395 		return ((struct username *)0);
396 	up = adduid(pwp->pw_uid);
397 	(void) strncpy(up->u_name, pwp->pw_name, sizeof (up->u_name));
398 	return (up);
399 }
400 
401 /*
402  * adduid() should *ONLY* be called from lookup in order
403  * to avoid duplicate entries.
404  */
405 static struct username *
406 adduid(uid_t uid)
407 {
408 	struct username *up, **uhp;
409 
410 	up = calloc(1, sizeof (struct username));
411 	if (up == 0) {
412 		(void) fprintf(stderr,
413 			"out of memory for username structures\n");
414 		exit(31+1);
415 	}
416 	uhp = &uhead[uid % UHASH];
417 	up->u_next = *uhp;
418 	*uhp = up;
419 	up->u_uid = uid;
420 	return (up);
421 }
422 
423 static void
424 usage(void)
425 {
426 	(void) fprintf(stderr, "ufs usage:\n");
427 	(void) fprintf(stderr, "\trepquota [-v] -a \n");
428 	(void) fprintf(stderr, "\trepquota [-v] filesys ...\n");
429 	exit(31+1);
430 }
431 
432 static int
433 quotactl(int cmd, char *special, uid_t uid, caddr_t addr)
434 {
435 	int		fd;
436 	int		status;
437 	struct quotctl	quota;
438 	char		qfile[MAXPATHLEN];
439 	FILE		*fstab;
440 	struct mnttab	mntp;
441 
442 
443 	if ((special == NULL) && (cmd == Q_ALLSYNC)) {
444 	/*
445 	 * Find the mount point of the special device.   This is
446 	 * because the ioctl that implements the quotactl call has
447 	 * to go to a real file, and not to the block device.
448 	 */
449 		if ((fstab = fopen(MNTTAB, "r")) == NULL) {
450 			(void) fprintf(stderr, "%s: ", MNTTAB);
451 			perror("open");
452 			exit(31+1);
453 		}
454 		fd = -1;
455 		while ((status = getmntent(fstab, &mntp)) == NULL) {
456 
457 			if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 ||
458 			    hasmntopt(&mntp, MNTOPT_RO))
459 				continue;
460 
461 			if ((strlcpy(qfile, mntp.mnt_mountp,
462 				sizeof (qfile)) >= sizeof (qfile)) ||
463 			    (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >=
464 				sizeof (qfile))) {
465 				continue;
466 			}
467 
468 			/* If we find *ANY* valid "quotas" file, use it */
469 			if ((fd = open64(qfile, O_RDONLY)) >= 0)
470 				break;
471 		}
472 		(void) fclose(fstab);
473 		if (fd == -1) {
474 			errno = ENOENT;
475 			(void) printf("quotactl: no quotas file "
476 				"on any mounted file system\n");
477 			return (-1);
478 		}
479 	}
480 	quota.op = cmd;
481 	quota.uid = uid;
482 	quota.addr = addr;
483 	status = ioctl(fd, Q_QUOTACTL, &quota);
484 	(void) close(fd);
485 	return (status);
486 }
487 
488 static char *
489 hasvfsopt(struct vfstab *vfs, char *opt)
490 {
491 	char *f, *opts;
492 	static char *tmpopts;
493 
494 	if (tmpopts == 0) {
495 		tmpopts = calloc(256, sizeof (char));
496 		if (tmpopts == 0)
497 			return (0);
498 	}
499 	(void) strcpy(tmpopts, vfs->vfs_mntopts);
500 	opts = tmpopts;
501 	f = mntopt(&opts);
502 	for (; *f; f = mntopt(&opts)) {
503 		if (strncmp(opt, f, strlen(opt)) == 0)
504 			return (f - tmpopts + vfs->vfs_mntopts);
505 	}
506 	return (NULL);
507 }
508