quota.c revision 1.14 1 /* $NetBSD: quota.c,v 1.14 1997/08/25 19:31:59 kleink Exp $ */
2
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";*/
47 static char rcsid[] = "$NetBSD: quota.c,v 1.14 1997/08/25 19:31:59 kleink Exp $";
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;
97 int ch;
98 extern char *optarg;
99 extern int optind, errno;
100
101 while ((ch = getopt(argc, argv, "ugvq")) != -1) {
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",
210 name, pwd->pw_uid);
211 return;
212 }
213 showquotas(USRQUOTA, pwd->pw_uid, name);
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",
280 name, grp->gr_gid);
281 return;
282 }
283 }
284 showquotas(GRPQUOTA, grp->gr_gid, name);
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 (void)snprintf(buf, sizeof buf, "%ddays", (hours + 12) / 24);
415 return (buf);
416 }
417 if (minutes >= 60) {
418 (void)snprintf(buf, sizeof buf, "%2d:%d", minutes / 60, minutes % 60);
419 return (buf);
420 }
421 (void)snprintf(buf, sizeof 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;
457 } else if (strncmp(fst[i].f_fstypename, "ffs",
458 MFSNAMELEN) == 0) {
459 /*
460 * XXX
461 * UFS filesystems must be in /etc/fstab, and must
462 * indicate that they have quotas on (?!) This is quite
463 * unlike SunOS where quotas can be enabled/disabled
464 * on a filesystem independent of /etc/fstab, and it
465 * will still print quotas for them.
466 */
467 if ((fs = getfsspec(fst[i].f_mntfromname)) == NULL)
468 continue;
469 if (getufsquota(&fst[i], fs, qup, id, quotatype) == 0)
470 continue;
471 } else
472 continue;
473 (void)strncpy(qup->fsname, fst[i].f_mntonname,
474 sizeof(qup->fsname) - 1);
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) {
502 (void)snprintf(usrname, sizeof usrname, "%s%s",
503 qfextension[USRQUOTA], qfname);
504 (void)snprintf(grpname, sizeof grpname, "%s%s",
505 qfextension[GRPQUOTA], qfname);
506 initname = 1;
507 }
508 (void)strncpy(buf, fs->fs_mntops, sizeof(buf) - 1);
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 }
523 (void)snprintf(buf, sizeof buf, "%s/%s.%s", fs->fs_file, qfname,
524 qfextension[type]);
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)), SEEK_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 }
708