1 /* $NetBSD: quota.c,v 1.13 1997/02/11 09:29:26 mrg 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.13 1997/02/11 09:29:26 mrg 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)), 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 } 708