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