Annotation of src/usr.bin/quota/quota.c, Revision 1.8
1.8 ! millert 1: /* $OpenBSD: quota.c,v 1.7 1996/08/07 14:08:52 etheisen Exp $ */
1.5 deraadt 2:
1.1 deraadt 3: /*
4: * Copyright (c) 1980, 1990, 1993
5: * The Regents of the University of California. All rights reserved.
6: *
7: * This code is derived from software contributed to Berkeley by
8: * Robert Elz at The University of Melbourne.
9: *
10: * Redistribution and use in source and binary forms, with or without
11: * modification, are permitted provided that the following conditions
12: * are met:
13: * 1. Redistributions of source code must retain the above copyright
14: * notice, this list of conditions and the following disclaimer.
15: * 2. Redistributions in binary form must reproduce the above copyright
16: * notice, this list of conditions and the following disclaimer in the
17: * documentation and/or other materials provided with the distribution.
18: * 3. All advertising materials mentioning features or use of this software
19: * must display the following acknowledgement:
20: * This product includes software developed by the University of
21: * California, Berkeley and its contributors.
22: * 4. Neither the name of the University nor the names of its contributors
23: * may be used to endorse or promote products derived from this software
24: * without specific prior written permission.
25: *
26: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36: * SUCH DAMAGE.
37: */
38:
39: #ifndef lint
40: static char copyright[] =
41: "@(#) Copyright (c) 1980, 1990, 1993\n\
42: The Regents of the University of California. All rights reserved.\n";
43: #endif /* not lint */
44:
45: #ifndef lint
46: /*static char sccsid[] = "from: @(#)quota.c 8.1 (Berkeley) 6/6/93";*/
1.8 ! millert 47: static char rcsid[] = "$OpenBSD: quota.c,v 1.7 1996/08/07 14:08:52 etheisen Exp $";
1.1 deraadt 48: #endif /* not lint */
49:
50: /*
51: * Disk quota reporting program.
52: */
53: #include <sys/param.h>
54: #include <sys/types.h>
55: #include <sys/file.h>
56: #include <sys/stat.h>
57: #include <sys/mount.h>
58: #include <sys/socket.h>
59: #include <ufs/ufs/quota.h>
60: #include <stdio.h>
61: #include <stdlib.h>
62: #include <fstab.h>
63: #include <ctype.h>
64: #include <string.h>
65: #include <pwd.h>
66: #include <grp.h>
67: #include <errno.h>
68:
69: #include <netdb.h>
70: #include <rpc/rpc.h>
71: #include <rpc/pmap_prot.h>
72: #include <rpcsvc/rquota.h>
73:
74: char *qfname = QUOTAFILENAME;
75: char *qfextension[] = INITQFNAMES;
76:
77: struct quotause {
78: struct quotause *next;
79: long flags;
80: struct dqblk dqblk;
81: char fsname[MAXPATHLEN + 1];
82: };
83: #define FOUND 0x01
84:
85: char *timeprt __P((time_t seconds));
86: struct quotause *getprivs __P((long id, int quotatype));
87:
88: int qflag;
89: int vflag;
90:
91: main(argc, argv)
92: char *argv[];
93: {
94: int ngroups;
95: gid_t mygid, gidset[NGROUPS];
96: int i, gflag = 0, uflag = 0;
1.3 deraadt 97: int ch;
1.1 deraadt 98: extern char *optarg;
99: extern int optind, errno;
100:
1.3 deraadt 101: while ((ch = getopt(argc, argv, "ugvq")) != -1) {
1.1 deraadt 102: switch(ch) {
103: case 'g':
104: gflag++;
105: break;
106: case 'u':
107: uflag++;
108: break;
109: case 'v':
110: vflag++;
111: break;
112: case 'q':
113: qflag++;
114: break;
115: default:
116: usage();
117: }
118: }
119: argc -= optind;
120: argv += optind;
121: if (!uflag && !gflag)
122: uflag++;
123: if (argc == 0) {
124: if (uflag)
125: showuid(getuid());
126: if (gflag) {
127: mygid = getgid();
128: ngroups = getgroups(NGROUPS, gidset);
129: if (ngroups < 0) {
130: perror("quota: getgroups");
131: exit(1);
132: }
133: showgid(mygid);
134: for (i = 0; i < ngroups; i++)
135: if (gidset[i] != mygid)
136: showgid(gidset[i]);
137: }
138: exit(0);
139: }
140: if (uflag && gflag)
141: usage();
142: if (uflag) {
143: for (; argc > 0; argc--, argv++) {
144: if (alldigits(*argv))
145: showuid(atoi(*argv));
146: else
147: showusrname(*argv);
148: }
149: exit(0);
150: }
151: if (gflag) {
152: for (; argc > 0; argc--, argv++) {
153: if (alldigits(*argv))
154: showgid(atoi(*argv));
155: else
156: showgrpname(*argv);
157: }
158: exit(0);
159: }
160: }
161:
162: usage()
163: {
164:
165: fprintf(stderr, "%s\n%s\n%s\n",
166: "Usage: quota [-guqv]",
167: "\tquota [-qv] -u username ...",
168: "\tquota [-qv] -g groupname ...");
169: exit(1);
170: }
171:
172: /*
173: * Print out quotas for a specified user identifier.
174: */
175: showuid(uid)
176: u_long uid;
177: {
178: struct passwd *pwd = getpwuid(uid);
179: u_long myuid;
180: char *name;
181:
182: if (pwd == NULL)
183: name = "(no account)";
184: else
185: name = pwd->pw_name;
186: myuid = getuid();
187: if (uid != myuid && myuid != 0) {
188: printf("quota: %s (uid %d): permission denied\n", name, uid);
189: return;
190: }
191: showquotas(USRQUOTA, uid, name);
192: }
193:
194: /*
195: * Print out quotas for a specifed user name.
196: */
197: showusrname(name)
198: char *name;
199: {
200: struct passwd *pwd = getpwnam(name);
201: u_long myuid;
202:
203: if (pwd == NULL) {
204: fprintf(stderr, "quota: %s: unknown user\n", name);
205: return;
206: }
207: myuid = getuid();
208: if (pwd->pw_uid != myuid && myuid != 0) {
209: fprintf(stderr, "quota: %s (uid %d): permission denied\n",
1.8 ! millert 210: pwd->pw_name, pwd->pw_uid);
1.1 deraadt 211: return;
212: }
1.8 ! millert 213: showquotas(USRQUOTA, pwd->pw_uid, pwd->pw_name);
1.1 deraadt 214: }
215:
216: /*
217: * Print out quotas for a specified group identifier.
218: */
219: showgid(gid)
220: u_long gid;
221: {
222: struct group *grp = getgrgid(gid);
223: int ngroups;
224: gid_t mygid, gidset[NGROUPS];
225: register int i;
226: char *name;
227:
228: if (grp == NULL)
229: name = "(no entry)";
230: else
231: name = grp->gr_name;
232: mygid = getgid();
233: ngroups = getgroups(NGROUPS, gidset);
234: if (ngroups < 0) {
235: perror("quota: getgroups");
236: return;
237: }
238: if (gid != mygid) {
239: for (i = 0; i < ngroups; i++)
240: if (gid == gidset[i])
241: break;
242: if (i >= ngroups && getuid() != 0) {
243: fprintf(stderr,
244: "quota: %s (gid %d): permission denied\n",
245: name, gid);
246: return;
247: }
248: }
249: showquotas(GRPQUOTA, gid, name);
250: }
251:
252: /*
253: * Print out quotas for a specifed group name.
254: */
255: showgrpname(name)
256: char *name;
257: {
258: struct group *grp = getgrnam(name);
259: int ngroups;
260: gid_t mygid, gidset[NGROUPS];
261: register int i;
262:
263: if (grp == NULL) {
264: fprintf(stderr, "quota: %s: unknown group\n", name);
265: return;
266: }
267: mygid = getgid();
268: ngroups = getgroups(NGROUPS, gidset);
269: if (ngroups < 0) {
270: perror("quota: getgroups");
271: return;
272: }
273: if (grp->gr_gid != mygid) {
274: for (i = 0; i < ngroups; i++)
275: if (grp->gr_gid == gidset[i])
276: break;
277: if (i >= ngroups && getuid() != 0) {
278: fprintf(stderr,
279: "quota: %s (gid %d): permission denied\n",
1.8 ! millert 280: grp->gr_name, grp->gr_gid);
1.1 deraadt 281: return;
282: }
283: }
1.8 ! millert 284: showquotas(GRPQUOTA, grp->gr_gid, grp->gr_name);
1.1 deraadt 285: }
286:
287: showquotas(type, id, name)
288: int type;
289: u_long id;
290: char *name;
291: {
292: register struct quotause *qup;
293: struct quotause *quplist;
294: char *msgi, *msgb, *nam;
295: int myuid, fd, lines = 0;
296: static int first;
297: static time_t now;
298:
299: if (now == 0)
300: time(&now);
301: quplist = getprivs(id, type);
302: for (qup = quplist; qup; qup = qup->next) {
303: if (!vflag &&
304: qup->dqblk.dqb_isoftlimit == 0 &&
305: qup->dqblk.dqb_ihardlimit == 0 &&
306: qup->dqblk.dqb_bsoftlimit == 0 &&
307: qup->dqblk.dqb_bhardlimit == 0)
308: continue;
309: msgi = (char *)0;
310: if (qup->dqblk.dqb_ihardlimit &&
311: qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit)
312: msgi = "File limit reached on";
313: else if (qup->dqblk.dqb_isoftlimit &&
314: qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit)
315: if (qup->dqblk.dqb_itime > now)
316: msgi = "In file grace period on";
317: else
318: msgi = "Over file quota on";
319: msgb = (char *)0;
320: if (qup->dqblk.dqb_bhardlimit &&
321: qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit)
322: msgb = "Block limit reached on";
323: else if (qup->dqblk.dqb_bsoftlimit &&
324: qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bsoftlimit)
325: if (qup->dqblk.dqb_btime > now)
326: msgb = "In block grace period on";
327: else
328: msgb = "Over block quota on";
329: if (qflag) {
330: if ((msgi != (char *)0 || msgb != (char *)0) &&
331: lines++ == 0)
332: heading(type, id, name, "");
333: if (msgi != (char *)0)
334: printf("\t%s %s\n", msgi, qup->fsname);
335: if (msgb != (char *)0)
336: printf("\t%s %s\n", msgb, qup->fsname);
337: continue;
338: }
339: if (vflag ||
340: qup->dqblk.dqb_curblocks ||
341: qup->dqblk.dqb_curinodes) {
342: if (lines++ == 0)
343: heading(type, id, name, "");
344: nam = qup->fsname;
345: if (strlen(qup->fsname) > 15) {
346: printf("%s\n", qup->fsname);
347: nam = "";
348: }
349: printf("%15s%8d%c%7d%8d%8s"
350: , nam
351: , dbtob(qup->dqblk.dqb_curblocks) / 1024
352: , (msgb == (char *)0) ? ' ' : '*'
353: , dbtob(qup->dqblk.dqb_bsoftlimit) / 1024
354: , dbtob(qup->dqblk.dqb_bhardlimit) / 1024
355: , (msgb == (char *)0) ? ""
356: : timeprt(qup->dqblk.dqb_btime));
357: printf("%8d%c%7d%8d%8s\n"
358: , qup->dqblk.dqb_curinodes
359: , (msgi == (char *)0) ? ' ' : '*'
360: , qup->dqblk.dqb_isoftlimit
361: , qup->dqblk.dqb_ihardlimit
362: , (msgi == (char *)0) ? ""
363: : timeprt(qup->dqblk.dqb_itime)
364: );
365: continue;
366: }
367: }
368: if (!qflag && lines == 0)
369: heading(type, id, name, "none");
370: }
371:
372: heading(type, id, name, tag)
373: int type;
374: u_long id;
375: char *name, *tag;
376: {
377:
378: printf("Disk quotas for %s %s (%cid %d): %s\n", qfextension[type],
379: name, *qfextension[type], id, tag);
380: if (!qflag && tag[0] == '\0') {
381: printf("%15s%8s %7s%8s%8s%8s %7s%8s%8s\n"
382: , "Filesystem"
383: , "blocks"
384: , "quota"
385: , "limit"
386: , "grace"
387: , "files"
388: , "quota"
389: , "limit"
390: , "grace"
391: );
392: }
393: }
394:
395: /*
396: * Calculate the grace period and return a printable string for it.
397: */
398: char *
399: timeprt(seconds)
400: time_t seconds;
401: {
402: time_t hours, minutes;
403: static char buf[20];
404: static time_t now;
405:
406: if (now == 0)
407: time(&now);
408: if (now > seconds)
409: return ("none");
410: seconds -= now;
411: minutes = (seconds + 30) / 60;
412: hours = (minutes + 30) / 60;
413: if (hours >= 36) {
414: sprintf(buf, "%ddays", (hours + 12) / 24);
415: return (buf);
416: }
417: if (minutes >= 60) {
418: sprintf(buf, "%2d:%d", minutes / 60, minutes % 60);
419: return (buf);
420: }
421: sprintf(buf, "%2d", minutes);
422: return (buf);
423: }
424:
425: /*
426: * Collect the requested quota information.
427: */
428: struct quotause *
429: getprivs(id, quotatype)
430: register long id;
431: int quotatype;
432: {
433: register struct quotause *qup, *quptail;
434: register struct fstab *fs;
435: struct quotause *quphead;
436: struct statfs *fst;
437: int nfst, i;
438:
439: qup = quphead = (struct quotause *)0;
440:
441: nfst = getmntinfo(&fst, MNT_WAIT);
442: if (nfst == 0) {
443: fprintf(stderr, "quota: no filesystems mounted!\n");
444: exit(2);
445: }
446: setfsent();
447: for (i=0; i<nfst; i++) {
448: if (qup == NULL) {
449: if ((qup = (struct quotause *)malloc(sizeof *qup)) == NULL) {
450: fprintf(stderr, "quota: out of memory\n");
451: exit(2);
452: }
453: }
454: if (strncmp(fst[i].f_fstypename, "nfs", MFSNAMELEN) == 0) {
455: if (getnfsquota(&fst[i], NULL, qup, id, quotatype) == 0)
456: continue;
1.4 deraadt 457: } else if (!strncmp(fst[i].f_fstypename, "ffs", MFSNAMELEN) ||
458: !strncmp(fst[i].f_fstypename, "ufs", MFSNAMELEN) ||
459: !strncmp(fst[i].f_fstypename, "mfs", MFSNAMELEN)) {
1.1 deraadt 460: /*
461: * XXX
462: * UFS filesystems must be in /etc/fstab, and must
463: * indicate that they have quotas on (?!) This is quite
464: * unlike SunOS where quotas can be enabled/disabled
465: * on a filesystem independent of /etc/fstab, and it
466: * will still print quotas for them.
467: */
468: if ((fs = getfsspec(fst[i].f_mntfromname)) == NULL)
469: continue;
470: if (getufsquota(&fst[i], fs, qup, id, quotatype) == 0)
471: continue;
472: } else
473: continue;
474: strcpy(qup->fsname, fst[i].f_mntonname);
475: if (quphead == NULL)
476: quphead = qup;
477: else
478: quptail->next = qup;
479: quptail = qup;
480: quptail->next = 0;
481: qup = NULL;
482: }
483: if (qup)
484: free(qup);
485: endfsent();
486: return (quphead);
487: }
488:
489: /*
490: * Check to see if a particular quota is to be enabled.
491: */
492: ufshasquota(fs, type, qfnamep)
493: register struct fstab *fs;
494: int type;
495: char **qfnamep;
496: {
497: static char initname, usrname[100], grpname[100];
498: static char buf[BUFSIZ];
499: char *opt, *cp;
500:
501: if (!initname) {
1.6 deraadt 502: snprintf(usrname, sizeof usrname, "%s%s",
503: qfextension[USRQUOTA], qfname);
504: snprintf(grpname, sizeof grpname, "%s%s",
505: qfextension[GRPQUOTA], qfname);
1.1 deraadt 506: initname = 1;
507: }
1.7 etheisen 508: strncpy(buf, fs->fs_mntops, sizeof buf);
1.1 deraadt 509: for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
510: if (cp = index(opt, '='))
511: *cp++ = '\0';
512: if (type == USRQUOTA && strcmp(opt, usrname) == 0)
513: break;
514: if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
515: break;
516: }
517: if (!opt)
518: return (0);
519: if (cp) {
520: *qfnamep = cp;
521: return (1);
522: }
1.6 deraadt 523: (void) snprintf(buf, sizeof buf, "%s/%s.%s",
524: fs->fs_file, qfname, qfextension[type]);
1.1 deraadt 525: *qfnamep = buf;
526: return (1);
527: }
528:
529: int
530: getufsquota(fst, fs, qup, id, quotatype)
531: struct statfs *fst;
532: struct fstab *fs;
533: struct quotause *qup;
534: long id;
535: int quotatype;
536: {
537: char *qfpathname;
538: int fd, qcmd;
539:
540: qcmd = QCMD(Q_GETQUOTA, quotatype);
541: if (!ufshasquota(fs, quotatype, &qfpathname))
542: return (0);
543:
544: if (quotactl(fs->fs_file, qcmd, id, &qup->dqblk) != 0) {
545: if ((fd = open(qfpathname, O_RDONLY)) < 0) {
546: perror(qfpathname);
547: return (0);
548: }
549: (void) lseek(fd, (off_t)(id * sizeof(struct dqblk)), L_SET);
550: switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) {
551: case 0: /* EOF */
552: /*
553: * Convert implicit 0 quota (EOF)
554: * into an explicit one (zero'ed dqblk)
555: */
556: bzero((caddr_t)&qup->dqblk, sizeof(struct dqblk));
557: break;
558: case sizeof(struct dqblk): /* OK */
559: break;
560: default: /* ERROR */
561: fprintf(stderr, "quota: read error");
562: perror(qfpathname);
563: close(fd);
564: return (0);
565: }
566: close(fd);
567: }
568: return (1);
569: }
570:
571: int
572: getnfsquota(fst, fs, qup, id, quotatype)
573: struct statfs *fst;
574: struct fstab *fs;
575: struct quotause *qup;
576: long id;
577: int quotatype;
578: {
579: struct getquota_args gq_args;
580: struct getquota_rslt gq_rslt;
581: struct dqblk *dqp = &qup->dqblk;
582: struct timeval tv;
583: char *cp;
584:
585: if (fst->f_flags & MNT_LOCAL)
586: return (0);
587:
588: /*
589: * rpc.rquotad does not support group quotas
590: */
591: if (quotatype != USRQUOTA)
592: return (0);
593:
594: /*
595: * must be some form of "hostname:/path"
596: */
597: cp = strchr(fst->f_mntfromname, ':');
598: if (cp == NULL) {
599: fprintf(stderr, "cannot find hostname for %s\n",
600: fst->f_mntfromname);
601: return (0);
602: }
603:
604: *cp = '\0';
605: if (*(cp+1) != '/') {
606: *cp = ':';
607: return (0);
608: }
609:
610: gq_args.gqa_pathp = cp + 1;
611: gq_args.gqa_uid = id;
612: if (callaurpc(fst->f_mntfromname, RQUOTAPROG, RQUOTAVERS,
613: RQUOTAPROC_GETQUOTA, xdr_getquota_args, &gq_args,
614: xdr_getquota_rslt, &gq_rslt) != 0) {
615: *cp = ':';
616: return (0);
617: }
618:
619: switch (gq_rslt.status) {
620: case Q_NOQUOTA:
621: break;
622: case Q_EPERM:
623: fprintf(stderr, "quota permission error, host: %s\n",
624: fst->f_mntfromname);
625: break;
626: case Q_OK:
627: gettimeofday(&tv, NULL);
628: /* blocks*/
629: dqp->dqb_bhardlimit =
630: gq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit *
631: gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE;
632: dqp->dqb_bsoftlimit =
633: gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit *
634: gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE;
635: dqp->dqb_curblocks =
636: gq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks *
637: gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE;
638: /* inodes */
639: dqp->dqb_ihardlimit =
640: gq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit;
641: dqp->dqb_isoftlimit =
642: gq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit;
643: dqp->dqb_curinodes =
644: gq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles;
645: /* grace times */
646: dqp->dqb_btime =
647: tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft;
648: dqp->dqb_itime =
649: tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft;
650: *cp = ':';
651: return (1);
652: default:
653: fprintf(stderr, "bad rpc result, host: %s\n",
654: fst->f_mntfromname);
655: break;
656: }
657: *cp = ':';
658: return (0);
659: }
660:
661: int
662: callaurpc(host, prognum, versnum, procnum, inproc, in, outproc, out)
663: char *host;
664: xdrproc_t inproc, outproc;
665: char *in, *out;
666: {
667: struct sockaddr_in server_addr;
668: enum clnt_stat clnt_stat;
669: struct hostent *hp;
670: struct timeval timeout, tottimeout;
671:
672: CLIENT *client = NULL;
673: int socket = RPC_ANYSOCK;
674:
675: if ((hp = gethostbyname(host)) == NULL)
676: return ((int) RPC_UNKNOWNHOST);
677: timeout.tv_usec = 0;
678: timeout.tv_sec = 6;
679: bcopy(hp->h_addr, &server_addr.sin_addr, hp->h_length);
680: server_addr.sin_family = AF_INET;
681: server_addr.sin_port = 0;
682:
683: if ((client = clntudp_create(&server_addr, prognum,
684: versnum, timeout, &socket)) == NULL)
685: return ((int) rpc_createerr.cf_stat);
686:
687: client->cl_auth = authunix_create_default();
688: tottimeout.tv_sec = 25;
689: tottimeout.tv_usec = 0;
690: clnt_stat = clnt_call(client, procnum, inproc, in,
691: outproc, out, tottimeout);
692:
693: return ((int) clnt_stat);
694: }
695:
696: alldigits(s)
697: register char *s;
698: {
699: register c;
700:
701: c = *s++;
702: do {
703: if (!isdigit(c))
704: return (0);
705: } while (c = *s++);
706: return (1);
707: }